21#include <boost/test/data/test_case.hpp>
52 double aMinArea,
const wxString& aLabel )
54 for(
int ii = 0; ii < aFill->OutlineCount(); ++ii )
56 const double area =
std::abs( aFill->Outline( ii ).Area() );
58 BOOST_CHECK_MESSAGE( area >= aMinArea,
59 wxString::Format(
"%s %d area %.0f IU^2 below %.0f IU^2; partial "
60 "stamps should not survive.",
61 aLabel, ii, area, aMinArea ) );
91 for(
PAD*
pad : m_board->Footprints()[0]->Pads() )
93 if(
pad->GetNumber() ==
"2" ||
pad->GetNumber() ==
"4" ||
pad->GetNumber() ==
"6" )
101 for(
PCB_TRACK* track : m_board->Tracks() )
109 arc8 = track->m_Uuid;
110 track->SetWidth( track->GetWidth() +
delta +
delta );
114 arc12 = track->m_Uuid;
120 bool foundPad2Error =
false;
121 bool foundPad4Error =
false;
122 bool foundPad6Error =
false;
123 bool foundArc8Error =
false;
124 bool foundArc12Error =
false;
125 bool foundOtherError =
false;
130 [&](
const std::shared_ptr<DRC_ITEM>& aItem,
const VECTOR2I& aPos,
int aLayer,
131 const std::function<
void(
PCB_MARKER* )>& aPathGenerator )
135 BOARD_ITEM* item_a = m_board->ResolveItem( aItem->GetMainItemID() );
136 PAD* pad_a =
dynamic_cast<PAD*
>( item_a );
139 BOARD_ITEM* item_b = m_board->ResolveItem( aItem->GetAuxItemID() );
140 PAD* pad_b =
dynamic_cast<PAD*
>( item_b );
143 if( pad_a && pad_a->
GetNumber() ==
"2" ) foundPad2Error =
true;
144 else if( pad_a && pad_a->
GetNumber() ==
"4" ) foundPad4Error =
true;
145 else if( pad_a && pad_a->
GetNumber() ==
"6" ) foundPad6Error =
true;
146 else if( pad_b && pad_b->
GetNumber() ==
"2" ) foundPad2Error =
true;
147 else if( pad_b && pad_b->
GetNumber() ==
"4" ) foundPad4Error =
true;
148 else if( pad_b && pad_b->
GetNumber() ==
"6" ) foundPad6Error =
true;
149 else if( trk_a && trk_a->
m_Uuid == arc8 ) foundArc8Error =
true;
150 else if( trk_a && trk_a->
m_Uuid == arc12 ) foundArc12Error =
true;
151 else if( trk_b && trk_b->
m_Uuid == arc8 ) foundArc8Error =
true;
152 else if( trk_b && trk_b->
m_Uuid == arc12 ) foundArc12Error =
true;
153 else foundOtherError =
true;
183 for(
ZONE* zone : m_board->Zones() )
185 if( zone->GetLayerSet().Contains(
F_Cu ) )
222 std::vector<DRC_ITEM> violations;
225 [&](
const std::shared_ptr<DRC_ITEM>& aItem,
const VECTOR2I& aPos,
int aLayer,
226 const std::function<
void(
PCB_MARKER* )>& aPathGenerator )
229 violations.push_back( *aItem );
234 if( violations.empty() )
245 for(
const DRC_ITEM& item : violations )
248 BOOST_ERROR( wxString::Format(
"Zone fill regression: %s failed", relPath ) );
255 KI_TEST::LoadBoard( m_settingsManager,
"line_ending_zone_flood/line_ending_zone_flood", m_board );
261 std::vector<DRC_ITEM> violations;
264 [&](
const std::shared_ptr<DRC_ITEM>& aItem,
const VECTOR2I&,
int,
269 violations.push_back( *aItem );
276 if( violations.empty() )
285 for(
const DRC_ITEM& item : violations )
288 BOOST_ERROR( wxString::Format(
"Zone fill line-ending regression failed with %d clearance/shorting violations",
289 (
int) violations.size() ) );
311 ~ScopeGuard() { ref = orig; }
314 auto runDrcClearanceCheck =
315 [
this](
bool aIterative ) ->
int
326 std::vector<DRC_ITEM> violations;
332 [&](
const std::shared_ptr<DRC_ITEM>& aItem,
const VECTOR2I& aPos,
int aLayer,
333 const std::function<
void(
PCB_MARKER* )>& aPathGenerator )
337 BOARD_ITEM* itemA = m_board->ResolveItem( aItem->GetMainItemID() );
338 BOARD_ITEM* itemB = m_board->ResolveItem( aItem->GetAuxItemID() );
340 if(
dynamic_cast<ZONE*
>( itemA ) &&
dynamic_cast<ZONE*
>( itemB ) )
342 violations.push_back( *aItem );
352 return static_cast<int>( violations.size() );
355 int iterativeViolations = runDrcClearanceCheck(
true );
357 BOOST_CHECK_MESSAGE( iterativeViolations == 0,
358 wxString::Format(
"Iterative refill produced %d zone-to-zone clearance "
359 "violations (expected 0)", iterativeViolations ) );
361 int nonIterativeViolations = runDrcClearanceCheck(
false );
363 BOOST_CHECK_MESSAGE( nonIterativeViolations == 0,
364 wxString::Format(
"Non-iterative refill produced %d zone-to-zone clearance "
365 "violations (expected 0)", nonIterativeViolations ) );
383 std::vector<DRC_ITEM> violations;
386 [&](
const std::shared_ptr<DRC_ITEM>& aItem,
const VECTOR2I& aPos,
int aLayer,
387 const std::function<
void(
PCB_MARKER* )>& aPathGenerator )
390 violations.push_back( *aItem );
395 if( violations.empty() )
398 BOOST_TEST_MESSAGE( wxString::Format(
"Zone fill copper sliver regression: %s passed", relPath ) );
406 for(
const DRC_ITEM& item : violations )
409 BOOST_ERROR( wxString::Format(
"Zone fill copper sliver regression: %s failed", relPath ) );
415 {
"teardrop_issue_JPC2", 5 },
422 const wxString& relPath =
test.first;
423 const int count =
test.second;
430 for(
ZONE* zone : m_board->Zones() )
432 if( zone->IsTeardropArea() )
436 BOOST_CHECK_MESSAGE( zoneCount == count,
"Expected " << count <<
" teardrop zones in " << relPath <<
", found "
444 std::vector<wxString> tests = { {
"issue19956/issue19956" }
447 for(
const wxString& relPath : tests )
452 for(
ZONE* zone : m_board->Zones() )
456 std::shared_ptr<SHAPE> a_shape( zone->GetEffectiveShape( layer ) );
458 for(
PAD*
pad : m_board->GetPads() )
460 std::shared_ptr<SHAPE> pad_shape(
pad->GetEffectiveShape( layer ) );
461 int clearance = pad_shape->GetClearance( a_shape.get() );
462 BOOST_CHECK_MESSAGE(
pad->GetNetCode() == zone->GetNetCode() ||
clearance != 0,
463 wxString::Format(
"Pad %s from Footprint %s has net code %s and "
464 "is connected to zone with net code %s",
466 pad->GetParentFootprint()->GetReferenceAsString(),
468 zone->GetNetname() ) );
502 ~ScopeGuard() { ref = orig; }
510 ZONE* gndZone =
nullptr;
512 for(
ZONE* zone : m_board->Zones() )
514 if( zone->GetNetname() ==
"GND" )
521 BOOST_REQUIRE_MESSAGE( gndZone !=
nullptr,
"GND zone not found in test board" );
525 bool hasOutline = m_board->GetBoardPolygonOutlines( boardOutline,
true );
526 BOOST_REQUIRE_MESSAGE( hasOutline,
"Board outline not found" );
528 double boardArea = 0.0;
539 double fillRatio = gndFilledArea / boardArea;
541 BOOST_TEST_MESSAGE( wxString::Format(
"Board area: %.2f sq mm, GND filled area: %.2f sq mm, Fill ratio: %.1f%%",
544 fillRatio * 100.0 ) );
546 BOOST_CHECK_MESSAGE( fillRatio >= 0.25,
547 wxString::Format(
"GND zone fill ratio %.1f%% is less than expected 25%%. This indicates "
548 "issue 21746 - lower priority zones not filling areas where higher "
549 "priority isolated islands were removed.",
550 fillRatio * 100.0 ) );
575 int viasWithUnreachableFlashing = 0;
576 int totalConditionalVias = 0;
578 PCB_LAYER_ID in1Cu = m_board->GetLayerID( wxT(
"In1.Cu" ) );
579 PCB_LAYER_ID in2Cu = m_board->GetLayerID( wxT(
"In2.Cu" ) );
581 for(
PCB_TRACK* track : m_board->Tracks() )
588 if( !
via->GetRemoveUnconnected() )
591 totalConditionalVias++;
594 bool flashedOnIn1 =
via->FlashLayer( in1Cu );
595 bool flashedOnIn2 =
via->FlashLayer( in2Cu );
597 if( !flashedOnIn1 && !flashedOnIn2 )
601 int holeRadius =
via->GetDrillValue() / 2;
604 bool zoneReachesVia =
false;
606 for(
ZONE* zone : m_board->Zones() )
608 if( zone->GetIsRuleArea() )
611 if( zone->GetNetCode() !=
via->GetNetCode() )
616 if( !zone->IsOnLayer( layer ) )
619 if( !zone->HasFilledPolysForLayer( layer ) )
622 const std::shared_ptr<SHAPE_POLY_SET>& fill = zone->GetFilledPolysList( layer );
624 if( fill->Contains( viaCenter, -1, holeRadius ) )
626 zoneReachesVia =
true;
636 if( !zoneReachesVia && ( flashedOnIn1 || flashedOnIn2 ) )
637 viasWithUnreachableFlashing++;
640 BOOST_TEST_MESSAGE( wxString::Format(
"Total conditional vias: %d, Vias with unreachable flashing: %d",
641 totalConditionalVias,
642 viasWithUnreachableFlashing ) );
644 BOOST_CHECK_MESSAGE( viasWithUnreachableFlashing == 0,
645 wxString::Format(
"Found %d vias flashed on zone layers where the zone fill doesn't "
646 "actually reach them. This indicates issue 22010 is not fixed.",
647 viasWithUnreachableFlashing ) );
669 int viasShortingZones = 0;
670 int totalConditionalVias = 0;
672 for(
PCB_TRACK* track : m_board->Tracks() )
679 if( !
via->GetRemoveUnconnected() )
682 totalConditionalVias++;
686 for(
ZONE* zone : m_board->Zones() )
688 if( zone->GetIsRuleArea() )
691 if( zone->GetNetCode() ==
via->GetNetCode() )
696 if( !
via->FlashLayer( layer ) )
699 if( !zone->HasFilledPolysForLayer( layer ) )
702 const std::shared_ptr<SHAPE_POLY_SET>& fill = zone->GetFilledPolysList( layer );
703 int viaRadius =
via->GetWidth( layer ) / 2;
705 if( fill->Contains( viaCenter, -1, viaRadius ) )
707 BOOST_TEST_MESSAGE( wxString::Format(
"Via at (%d, %d) on net %s is flashing on layer %s where "
708 "zone net %s is filled - this creates a short!",
709 viaCenter.
x, viaCenter.
y,
711 m_board->GetLayerName( layer ),
712 zone->GetNetname() ) );
719 BOOST_TEST_MESSAGE( wxString::Format(
"Total conditional vias: %d, Vias shorting zones: %d",
720 totalConditionalVias,
721 viasShortingZones ) );
723 BOOST_CHECK_MESSAGE( viasShortingZones == 0,
724 wxString::Format(
"Found %d vias flashed on layers where they short to zones with "
725 "different nets. This indicates issue 12964 is not fixed.",
726 viasShortingZones ) );
744 KI_TEST::LoadBoard( m_settingsManager,
"hatch_thermal_connectivity/hatch_thermal_connectivity",
749 m_board->BuildConnectivity();
751 int unconnectedCount = m_board->GetConnectivity()->GetUnconnectedCount(
false );
753 BOOST_CHECK_MESSAGE( unconnectedCount == 0,
754 wxString::Format(
"Found %d unconnected items after zone fill. Hatch zone thermal "
755 "reliefs should maintain connectivity even with large hatch gaps.",
756 unconnectedCount ) );
780 PCB_LAYER_ID in1Cu = m_board->GetLayerID( wxT(
"In1.Cu" ) );
782 ZONE* gndZone =
nullptr;
784 for(
ZONE* zone : m_board->Zones() )
786 if( zone->GetNetname() ==
"GND" && zone->IsOnLayer( in1Cu ) )
793 BOOST_REQUIRE_MESSAGE( gndZone !=
nullptr,
"GND zone on In1.Cu not found in test board" );
808 double zoneOutlineArea = gndZone->
Outline()->
Area();
810 BOOST_REQUIRE_MESSAGE( zoneOutlineArea > 0.0,
"Zone outline area must be positive" );
812 double fillArea = 0.0;
814 for(
int i = 0; i < fill->OutlineCount(); i++ )
815 fillArea +=
std::abs( fill->Outline( i ).Area() );
817 double fillRatio = fillArea / zoneOutlineArea;
821 BOOST_CHECK_GE( fillRatio, 0.90 );
848 ~ScopeGuard() { ref = orig; }
856 std::vector<ZONE*> keepouts;
858 for(
ZONE* zone : m_board->Zones() )
860 if( zone->GetIsRuleArea() && zone->GetDoNotAllowZoneFills() )
861 keepouts.push_back( zone );
864 BOOST_REQUIRE_MESSAGE( !keepouts.empty(),
"No zone keepouts found in test board" );
867 int violationCount = 0;
869 for(
ZONE* keepout : keepouts )
871 for(
PCB_LAYER_ID layer : keepout->GetLayerSet().Seq() )
876 for(
ZONE* zone : m_board->Zones() )
878 if( zone->GetIsRuleArea() )
881 if( !zone->IsOnLayer( layer ) )
884 if( !zone->HasFilledPolysForLayer( layer ) )
887 const std::shared_ptr<SHAPE_POLY_SET>& fill = zone->GetFilledPolysList( layer );
895 double intersectionArea = 0;
901 if( intersectionArea > 1e6 )
903 BOOST_TEST_MESSAGE( wxString::Format(
"Zone %s fill on layer %s overlaps keepout by %.2f sq mm",
905 m_board->GetLayerName( layer ),
906 intersectionArea / 1e6 ) );
914 BOOST_CHECK_MESSAGE( violationCount == 0,
915 wxString::Format(
"Found %d zone fills overlapping keepout areas. This indicates "
916 "issue 22809 - iterative refiller ignores zone keepouts.",
940 PCB_LAYER_ID in2Cu = m_board->GetLayerID( wxT(
"In2.Cu" ) );
941 int padsWithMissingFlashing = 0;
942 int totalConditionalPads = 0;
944 for(
FOOTPRINT* footprint : m_board->Footprints() )
946 for(
PAD*
pad : footprint->Pads() )
948 if( !
pad->GetRemoveUnconnected() )
951 if( !
pad->HasHole() )
954 if(
pad->GetNetname() !=
"VBUS_DUT" &&
pad->GetNetname() !=
"VBUS_DBG" )
957 totalConditionalPads++;
960 bool shouldFlash =
false;
962 for(
ZONE* zone : m_board->Zones() )
964 if( zone->GetIsRuleArea() )
967 if( zone->GetNetCode() !=
pad->GetNetCode() )
970 if( !zone->IsOnLayer( in2Cu ) )
973 if( zone->Outline()->Contains(
pad->GetPosition() ) )
980 if( shouldFlash && !
pad->FlashLayer( in2Cu ) )
982 BOOST_TEST_MESSAGE( wxString::Format(
"Pad %s at (%d, %d) on net %s is inside zone but not flashing "
985 pad->GetPosition().x,
986 pad->GetPosition().y,
987 pad->GetNetname() ) );
988 padsWithMissingFlashing++;
993 BOOST_TEST_MESSAGE( wxString::Format(
"Total conditional pads: %d, Pads with missing flashing: %d",
994 totalConditionalPads,
995 padsWithMissingFlashing ) );
997 BOOST_CHECK_MESSAGE( padsWithMissingFlashing == 0,
998 wxString::Format(
"Found %d TH pads that should flash on inner layers but don't. This "
999 "indicates issue 22826 is not fixed.",
1000 padsWithMissingFlashing ) );
1019 const PCB_LAYER_ID innerLayers[] = { m_board->GetLayerID( wxT(
"In1.Cu" ) ),
1020 m_board->GetLayerID( wxT(
"In2.Cu" ) ) };
1022 int padsWithMissingFlashing = 0;
1023 int totalConditionalPads = 0;
1025 for(
FOOTPRINT* footprint : m_board->Footprints() )
1027 for(
PAD*
pad : footprint->Pads() )
1029 if( !
pad->GetRemoveUnconnected() || !
pad->HasHole() )
1034 bool shouldFlash =
false;
1036 for(
ZONE* zone : m_board->Zones() )
1038 if( zone->GetIsRuleArea() || zone->GetNetCode() !=
pad->GetNetCode() )
1041 if( !zone->IsOnLayer( layer ) )
1044 if( zone->Outline()->Contains(
pad->GetPosition() ) )
1054 totalConditionalPads++;
1056 if( !
pad->FlashLayer( layer ) )
1058 BOOST_TEST_MESSAGE( wxString::Format(
"Pad %s (drill %.2f mm, spoke angle %.0f deg) on net %s "
1059 "is inside the zone but not flashing on %s",
1062 pad->GetThermalSpokeAngle().AsDegrees(),
1064 m_board->GetLayerName( layer ) ) );
1065 padsWithMissingFlashing++;
1071 BOOST_TEST_MESSAGE( wxString::Format(
"Pads inside inner-layer zones: %d, missing flashing: %d",
1072 totalConditionalPads,
1073 padsWithMissingFlashing ) );
1075 BOOST_CHECK_MESSAGE( padsWithMissingFlashing == 0,
1076 wxString::Format(
"Found %d TH pads inside a same-net inner-layer zone that lost their "
1077 "annular ring after fill. This indicates issue 24865 is not fixed.",
1078 padsWithMissingFlashing ) );
1096 toolMgr.
SetEnvironment( m_board.get(),
nullptr,
nullptr,
nullptr,
nullptr );
1106 if( !commit.
Empty() )
1110 int teardropCount = 0;
1111 bool foundBadTeardrop =
false;
1113 for(
ZONE* zone : m_board->Zones() )
1115 if( !zone->IsTeardropArea() )
1132 int concaveCount = 0;
1134 for(
int i = 0; i <
chain.PointCount(); i++ )
1136 int prev = ( i == 0 ) ?
chain.PointCount() - 1 : i - 1;
1137 int next = ( i + 1 ) %
chain.PointCount();
1143 int64_t cross = (int64_t)
v1.x *
v2.y - (int64_t)
v1.y *
v2.x;
1153 if( concaveCount > 5 )
1155 BOOST_TEST_MESSAGE( wxString::Format(
"Teardrop has %d concave vertices, indicating possible corner "
1158 foundBadTeardrop =
true;
1162 BOOST_CHECK_MESSAGE( teardropCount > 0,
"Expected at least one teardrop zone" );
1164 BOOST_CHECK_MESSAGE( !foundBadTeardrop,
"Found teardrop with excessive concave vertices, indicating "
1165 "issue 19405 - teardrop curve intersecting rounded rectangle corner" );
1179 toolMgr.
SetEnvironment( m_board.get(),
nullptr,
nullptr,
nullptr,
nullptr );
1188 if( !commit.
Empty() )
1191 int teardropCount = 0;
1192 bool foundSpike =
false;
1194 const int maxError = m_board->GetDesignSettings().m_MaxError;
1196 for(
ZONE* zone : m_board->Zones() )
1198 if( !zone->IsTeardropArea() )
1204 int netcode = zone->GetNetCode();
1212 for(
FOOTPRINT* fp : m_board->Footprints() )
1214 for(
PAD*
pad : fp->Pads() )
1216 if(
pad->GetNetCode() == netcode &&
pad->IsOnLayer( layer ) )
1217 pad->TransformShapeToPolygon( corridor, layer, 0, maxError,
ERROR_OUTSIDE );
1221 for(
PCB_TRACK* track : m_board->Tracks() )
1223 if( track->GetNetCode() == netcode && track->IsOnLayer( layer ) )
1224 track->TransformShapeToPolygon( corridor, layer, 0, maxError,
ERROR_OUTSIDE );
1236 double tdArea =
std::abs( zone->Outline()->Area() );
1238 double ratio = tdArea > 0 ? outArea / tdArea : 0.0;
1240 BOOST_TEST_MESSAGE( wxString::Format(
"Teardrop on layer %d: area %.0f, area outside corridor %.0f (%.1f%%)",
1249 BOOST_TEST_MESSAGE( wxString::Format(
"Teardrop on layer %d sweeps %.1f%% of its area outside the "
1250 "track/pad corridor (spike)",
1256 BOOST_CHECK_MESSAGE( teardropCount > 0,
"Expected at least one teardrop zone" );
1257 BOOST_CHECK_MESSAGE( !foundSpike,
"A teardrop vertex spikes outside the track/pad corridor it should follow" );
1274 toolMgr.
SetEnvironment( m_board.get(),
nullptr,
nullptr,
nullptr,
nullptr );
1283 if( !commit.
Empty() )
1290 for(
FOOTPRINT* fp : m_board->Footprints() )
1292 for(
PAD*
pad : fp->Pads() )
1297 maxReach = std::max( maxReach, prms.
m_TdMaxLen + std::min( size.
x, size.
y ) );
1303 int teardropCount = 0;
1304 bool foundSpike =
false;
1306 for(
ZONE* zone : m_board->Zones() )
1308 if( !zone->IsTeardropArea() )
1315 for(
const VECTOR2I& pt : zone->Outline()->Outline( 0 ).CPoints() )
1317 SEG::ecoord bestSq = std::numeric_limits<SEG::ecoord>::max();
1319 for(
PCB_TRACK* track : m_board->Tracks() )
1321 if( !track->IsOnLayer( layer ) )
1324 bestSq = std::min( bestSq,
SEG( track->GetStart(), track->GetEnd() ).
SquaredDistance( pt ) );
1327 double dist = std::sqrt( (
double) bestSq );
1329 if( dist > maxReach )
1332 BOOST_TEST_MESSAGE( wxString::Format(
"Teardrop vertex (%.4f, %.4f) is %.4f mm from the nearest "
1333 "track, max allowed %.4f mm",
1343 BOOST_CHECK_MESSAGE( teardropCount == 2, wxString::Format(
"Expected a teardrop on each of the two pads covering "
1344 "the track end, found %d",
1346 BOOST_CHECK_MESSAGE( !foundSpike,
"A teardrop reaches far past its anchor track, indicating it was built around "
1347 "the custom pad's anchor position instead of the copper the track enters" );
1356 toolMgr.
SetEnvironment( m_board.get(),
nullptr,
nullptr,
nullptr,
nullptr );
1365 if( !commit.
Empty() )
1369 BOOST_REQUIRE( !m_board->Footprints().front()->Pads().empty() );
1371 PAD*
pad = m_board->Footprints().front()->Pads().front();
1373 const int minEdgeLen = 2 * m_board->GetDesignSettings().m_MaxError;
1375 int teardropCount = 0;
1377 for(
ZONE* zone : m_board->Zones() )
1379 if( !zone->IsTeardropArea() )
1389 for(
int ii = 0; ii <
chain.SegmentCount(); ii++ )
1393 if( seg.
Length() < minEdgeLen )
1398 if( distSq > bestSq )
1407 for(
const VECTOR2I& pt : { throat.
A, throat.
B } )
1409 bool onTrack =
false;
1411 for(
PCB_TRACK* track : m_board->Tracks() )
1412 onTrack |= track->HitTest( pt, 0 );
1414 BOOST_CHECK_MESSAGE( onTrack, wxString::Format(
"Teardrop anchor (%.6f, %.6f) mm is off the "
1420 BOOST_CHECK_MESSAGE( teardropCount == 1, wxString::Format(
"Expected 1 teardrop zone, found %d", teardropCount ) );
1437 toolMgr.
SetEnvironment( m_board.get(),
nullptr,
nullptr,
nullptr,
nullptr );
1447 if( !commit.
Empty() )
1451 int teardropCount = 0;
1452 bool foundBadTeardrop =
false;
1454 for(
ZONE* zone : m_board->Zones() )
1456 if( !zone->IsTeardropArea() )
1470 int concaveCount = 0;
1472 for(
int i = 0; i <
chain.PointCount(); i++ )
1474 int prev = ( i == 0 ) ?
chain.PointCount() - 1 : i - 1;
1475 int next = ( i + 1 ) %
chain.PointCount();
1480 int64_t cross = (int64_t)
v1.x *
v2.y - (int64_t)
v1.y *
v2.x;
1486 if( concaveCount > 5 )
1488 BOOST_TEST_MESSAGE( wxString::Format(
"Oval teardrop has %d concave vertices", concaveCount ) );
1489 foundBadTeardrop =
true;
1493 BOOST_CHECK_MESSAGE( teardropCount > 0,
"Expected at least one teardrop zone" );
1495 BOOST_CHECK_MESSAGE( !foundBadTeardrop,
"Found teardrop with excessive concave vertices on oval pad, "
1496 "indicating curve is not tangent to semicircular end" );
1513 toolMgr.
SetEnvironment( m_board.get(),
nullptr,
nullptr,
nullptr,
nullptr );
1523 if( !commit.
Empty() )
1527 PAD* largePad =
nullptr;
1529 for(
FOOTPRINT* fp : m_board->Footprints() )
1531 for(
PAD*
pad : fp->Pads() )
1541 BOOST_REQUIRE_MESSAGE( largePad !=
nullptr,
"Expected a circular pad in test board" );
1547 int teardropCount = 0;
1548 bool foundBadTeardrop =
false;
1550 for(
ZONE* zone : m_board->Zones() )
1552 if( !zone->IsTeardropArea() )
1565 int concaveCount = 0;
1567 for(
int i = 0; i <
chain.PointCount(); i++ )
1569 int prev = ( i == 0 ) ?
chain.PointCount() - 1 : i - 1;
1570 int next = ( i + 1 ) %
chain.PointCount();
1575 int64_t cross = (int64_t)
v1.x *
v2.y - (int64_t)
v1.y *
v2.x;
1581 if( concaveCount > 5 )
1583 BOOST_TEST_MESSAGE( wxString::Format(
"Large circle teardrop has %d concave vertices", concaveCount ) );
1584 foundBadTeardrop =
true;
1589 int maxError = m_board->GetDesignSettings().m_MaxError;
1591 for(
int i = 0; i <
chain.PointCount(); i++ )
1594 double dist = ( pt - padCenter ).EuclideanNorm();
1597 if( dist > padRadius * 0.5 && dist < padRadius * 1.5 )
1599 double deviation =
std::abs( dist - padRadius );
1602 if( deviation > maxError * 5 && deviation < padRadius * 0.2 )
1605 "Teardrop point at distance %.2f from pad center (radius %.2f), "
1606 "deviation %.2f exceeds tolerance",
1607 dist / 1000.0, padRadius / 1000.0, deviation / 1000.0 ) );
1613 BOOST_CHECK_MESSAGE( teardropCount > 0,
"Expected at least one teardrop zone" );
1615 BOOST_CHECK_MESSAGE( !foundBadTeardrop,
1616 "Found teardrop with excessive concave vertices on large circle, "
1617 "indicating anchor points may not be on circle edge" );
1641 for(
ZONE* zone : m_board->Zones() )
1643 if( zone->GetIsRuleArea() )
1648 if( !zone->HasFilledPolysForLayer( layer ) )
1651 const std::shared_ptr<SHAPE_POLY_SET>& fill = zone->GetFilledPolysList( layer );
1653 for(
PAD*
pad : m_board->GetPads() )
1655 if( !
pad->IsOnLayer( layer ) )
1658 if(
pad->GetNetCode() == zone->GetNetCode() )
1661 std::shared_ptr<SHAPE> padShape =
pad->GetEffectiveShape( layer );
1662 int clearance = padShape->GetClearance( fill.get() );
1667 "Pad %s (net %s) at (%d, %d) has zero clearance to zone %s "
1669 pad->GetNumber(),
pad->GetNetname(),
1670 pad->GetPosition().x,
pad->GetPosition().y,
1671 zone->GetNetname(), m_board->GetLayerName( layer ) ) );
1678 BOOST_CHECK_MESSAGE( violations == 0,
1679 wxString::Format(
"Found %d pads with missing zone clearance. "
1680 "Coincident pads with different nets must not be "
1681 "deduplicated in zone fill knockout.",
1703 std::vector<DRC_ITEM> violations;
1708 [&](
const std::shared_ptr<DRC_ITEM>& aItem,
const VECTOR2I& aPos,
int aLayer,
1709 const std::function<
void(
PCB_MARKER* )>& aPathGenerator )
1713 BOARD_ITEM* item_a = m_board->ResolveItem( aItem->GetMainItemID() );
1714 BOARD_ITEM* item_b = m_board->ResolveItem( aItem->GetAuxItemID() );
1716 ZONE* zone_a =
dynamic_cast<ZONE*
>( item_a );
1717 ZONE* zone_b =
dynamic_cast<ZONE*
>( item_b );
1719 if( zone_a || zone_b )
1720 violations.push_back( *aItem );
1747 ZONE* hvZone =
nullptr;
1748 ZONE* lvZone =
nullptr;
1750 for(
ZONE* zone : m_board->Zones() )
1752 if( zone->GetNetname() ==
"HV_NET" )
1754 else if( zone->GetNetname() ==
"LV_NET" )
1765 <<
" (expected " <<
pcbIUScale.mmToIU( 4.6 ) <<
")" );
1772 <<
" (expected " <<
pcbIUScale.mmToIU( 2.3 ) <<
")" );
1779 std::vector<DRC_ITEM> violations;
1784 [&](
const std::shared_ptr<DRC_ITEM>& aItem,
const VECTOR2I& aPos,
int aLayer,
1785 const std::function<
void(
PCB_MARKER* )>& aPathGenerator )
1789 BOARD_ITEM* item_a = m_board->ResolveItem( aItem->GetMainItemID() );
1790 BOARD_ITEM* item_b = m_board->ResolveItem( aItem->GetAuxItemID() );
1792 ZONE* zone_a =
dynamic_cast<ZONE*
>( item_a );
1793 ZONE* zone_b =
dynamic_cast<ZONE*
>( item_b );
1795 if( zone_a && zone_b )
1798 << aLayer <<
": " << aItem->GetErrorMessage(
true ) );
1799 violations.push_back( *aItem );
1812 KI_TEST::LoadBoard( m_settingsManager,
"issue23332_min_width/issue23332_min_width", m_board );
1818 for(
ZONE* zone : m_board->Zones() )
1820 int half_min_width = zone->GetMinThickness() / 2;
1827 if( !zone->HasFilledPolysForLayer( layer ) )
1830 std::shared_ptr<SHAPE_POLY_SET> fill = zone->GetFilledPolysList( layer );
1832 if( !fill || fill->OutlineCount() == 0 )
1837 for(
int ii = 0; ii < fill->OutlineCount(); ii++ )
1842 for(
int jj = 0; jj < fill->HoleCount( ii ); jj++ )
1843 island.
AddHole( fill->Hole( ii, jj ) );
1845 double originalArea = island.
Area();
1847 if( originalArea <= 0 )
1856 double prunedArea =
test.Area();
1857 double areaLoss = ( originalArea - prunedArea ) / originalArea;
1859 BOOST_TEST_MESSAGE( wxString::Format(
"Zone %s layer %d island %d: area=%.0f, loss=%.4f%%",
1861 static_cast<int>( layer ),
1864 areaLoss * 100.0 ) );
1866 BOOST_CHECK_MESSAGE( areaLoss < 0.01,
1867 wxString::Format(
"Zone %s layer %d island %d lost %.2f%% area from "
1868 "min-width pruning (min_width=%.3fmm)",
1870 static_cast<int>( layer ),
1873 zone->GetMinThickness() / (
double)
pcbIUScale.IU_PER_MM ) );
1888 for(
ZONE* zone : m_board->Zones() )
1890 int half_min_width = zone->GetMinThickness() / 2;
1897 if( !zone->HasFilledPolysForLayer( layer ) )
1900 std::shared_ptr<SHAPE_POLY_SET> fill = zone->GetFilledPolysList( layer );
1902 if( !fill || fill->OutlineCount() == 0 )
1905 for(
int ii = 0; ii < fill->OutlineCount(); ii++ )
1910 for(
int jj = 0; jj < fill->HoleCount( ii ); jj++ )
1911 island.
AddHole( fill->Hole( ii, jj ) );
1913 double originalArea = island.
Area();
1915 if( originalArea <= 0 )
1924 double prunedArea =
test.Area();
1925 double areaLoss = ( originalArea - prunedArea ) / originalArea;
1927 BOOST_CHECK_MESSAGE( areaLoss < 0.01,
1928 wxString::Format(
"Zone %s (priority %d) layer %d island %d lost %.2f%% area "
1929 "from min-width pruning, suggesting degenerate geometry "
1930 "from overlapping same-net zones",
1932 zone->GetAssignedPriority(),
1933 static_cast<int>( layer ),
1935 areaLoss * 100.0 ) );
1951 ~ScopeGuard() { ref = orig; }
1954 auto runAreaLossCheck =
1955 [
this](
bool aIterative )
1965 for(
ZONE* zone : m_board->Zones() )
1967 int half_min_width = zone->GetMinThickness() / 2;
1974 if( !zone->HasFilledPolysForLayer( layer ) )
1977 std::shared_ptr<SHAPE_POLY_SET> fill = zone->GetFilledPolysList( layer );
1979 if( !fill || fill->OutlineCount() == 0 )
1982 for(
int ii = 0; ii < fill->OutlineCount(); ii++ )
1987 for(
int jj = 0; jj < fill->HoleCount( ii ); jj++ )
1988 island.
AddHole( fill->Hole( ii, jj ) );
1990 double originalArea = island.
Area();
1992 if( originalArea <= 0 )
2003 double prunedArea =
test.Area();
2004 double areaLoss = ( originalArea - prunedArea ) / originalArea;
2006 BOOST_CHECK_MESSAGE( areaLoss < 0.01,
2007 wxString::Format(
"Zone %s (priority %d) layer %d island %d lost "
2008 "%.2f%% area (iterative=%d), suggesting degenerate "
2009 "geometry from different-net zone knockouts",
2011 zone->GetAssignedPriority(),
2012 static_cast<int>( layer ),
2021 runAreaLossCheck(
false );
2022 runAreaLossCheck(
true );
2051 ~ScopeGuard() { ref = orig; }
2054 KI_TEST::LoadBoard( m_settingsManager,
"issue23535_minimal/issue23535_minimal", m_board );
2058 ZONE* gndZone =
nullptr;
2060 for(
ZONE* zone : m_board->Zones() )
2062 if( zone->GetNetname() ==
"GND" )
2080 bool hasSpokeToNowhere = gndFill->Contains( spokeTestPoint );
2082 BOOST_CHECK_MESSAGE( !hasSpokeToNowhere,
"GND zone fill contains copper at the thermal gap test point (7.4, 5.0), "
2083 "indicating a thermal relief spoke to nowhere (issue 23535)." );
2090 bool hasValidSpoke = gndFill->Contains( validSpokePoint );
2092 BOOST_CHECK_MESSAGE( hasValidSpoke,
"GND zone fill does not contain copper at the valid spoke test point "
2093 "(5.6, 5.0). The fix may have incorrectly removed valid spokes." );
2111 toolMgr.
SetEnvironment( m_board.get(),
nullptr,
nullptr,
nullptr,
nullptr );
2120 if( !commit.
Empty() )
2128 int teardropCount = 0;
2130 for(
ZONE* zone : m_board->Zones() )
2132 if( !zone->IsTeardropArea() )
2150 for(
int i = 0; i <
chain.PointCount(); i++ )
2152 int dy =
chain.CPoint( i ).y - trackY;
2155 maxAbove = std::max( maxAbove, -dy );
2157 maxBelow = std::max( maxBelow, dy );
2161 BOOST_CHECK_MESSAGE( maxAbove > 0 && maxBelow > 0,
2162 "Teardrop should extend on both sides of the track axis" );
2164 if( maxAbove > 0 && maxBelow > 0 )
2168 double ratio =
static_cast<double>( std::min( maxAbove, maxBelow ) )
2169 /
static_cast<double>( std::max( maxAbove, maxBelow ) );
2171 BOOST_CHECK_MESSAGE( ratio > 0.7,
2172 wxString::Format(
"Teardrop asymmetry ratio %.2f is too low (above=%d, below=%d). "
2173 "Expected roughly symmetric about the track axis.",
2180 BOOST_CHECK_MESSAGE( teardropCount > 0,
"Expected at least one teardrop zone for off-center track" );
2195 aBoard.
Add( track );
2208 toolMgr.
SetEnvironment( &aBoard,
nullptr,
nullptr,
nullptr,
nullptr );
2217 if( !commit.
Empty() )
2227 if( !zone->IsTeardropArea() )
2232 for(
const VECTOR2I& pt : zone->Outline()->COutline( 0 ).CPoints() )
2234 BOOST_CHECK_MESSAGE( aTrack->
HitTest( pt, tolerance ) || aCrossed->
HitTest( pt, tolerance ),
2235 wxString::Format(
"Teardrop corner (%.3f, %.3f) mm lies outside both "
2236 "the track and the crossed item",
2258 via->GetTeardropParams().m_Enabled =
true;
2259 via->GetTeardropParams().m_CurvedEdges =
false;
2261 via->SetNetCode( 1 );
2283 pad->GetTeardropParams().m_Enabled =
true;
2284 pad->GetTeardropParams().m_CurvedEdges =
false;
2286 board.
Add( footprint );
2287 pad->SetNetCode( 1 );
2306 toolMgr.
SetEnvironment( m_board.get(),
nullptr,
nullptr,
nullptr,
nullptr );
2315 if( !commit.
Empty() )
2321 PAD* testPad =
nullptr;
2323 for(
FOOTPRINT* fp : m_board->Footprints() )
2325 for(
PAD*
pad : fp->Pads() )
2327 if(
pad->GetNumber() ==
"7" )
2335 BOOST_REQUIRE_MESSAGE( testPad !=
nullptr,
"Could not find pad 7 in test board" );
2338 int tolerance = std::max( m_board->GetDesignSettings().m_MaxError,
pcbIUScale.mmToIU( 0.001 ) );
2343 int teardropCount = 0;
2345 for(
ZONE* zone : m_board->Zones() )
2347 if( !zone->IsTeardropArea() )
2352 BOOST_REQUIRE_MESSAGE( outline && outline->
OutlineCount() > 0,
"Teardrop zone has no outline" );
2366 for(
int i = 0; i <
chain.PointCount(); i++ )
2369 double distToPad = (
VECTOR2D( pt ) -
VECTOR2D( padCenter ) ).EuclideanNorm();
2370 double distToTrack = (
VECTOR2D( pt ) -
VECTOR2D( trackAnchor ) ).EuclideanNorm();
2373 if( distToPad < distToTrack )
2375 BOOST_CHECK_MESSAGE( padPoly.
Contains( pt ),
2376 wxString::Format(
"Teardrop vertex (%d, %d) is outside the pad outline with "
2384 BOOST_CHECK_MESSAGE( teardropCount > 0,
"Expected at least one teardrop zone for elongated pad" );
2399 [&](
bool aCurvedEdges )
2403 for(
PCB_TRACK* track : m_board->Tracks() )
2407 static_cast<PCB_VIA*
>( track )->SetTeardropCurved( aCurvedEdges );
2413 toolMgr.
SetEnvironment( m_board.get(),
nullptr,
nullptr,
nullptr,
nullptr );
2422 if( !commit.
Empty() )
2425 int teardropCount = 0;
2426 bool foundSelfIntersection =
false;
2428 for(
ZONE* zone : m_board->Zones() )
2430 if( !zone->IsTeardropArea() )
2441 int n =
chain.PointCount();
2443 for(
int i = 0; i < n && !foundSelfIntersection; i++ )
2445 SEG segA(
chain.CPoint( i ),
chain.CPoint( ( i + 1 ) % n ) );
2447 for(
int j = i + 2; j < n; j++ )
2449 if( i == 0 && j == n - 1 )
2452 SEG segB(
chain.CPoint( j ),
chain.CPoint( ( j + 1 ) % n ) );
2455 if( hit.has_value() )
2458 "%d and %d (curved=%s)",
2462 aCurvedEdges ?
"yes" :
"no" ) );
2464 for(
int k = 0; k < n; k++ )
2467 chain.CPoint( k ).x,
chain.CPoint( k ).y ) );
2470 foundSelfIntersection =
true;
2477 BOOST_CHECK_MESSAGE( teardropCount > 0,
2478 wxString::Format(
"Expected at least one teardrop zone (curved=%s)",
2479 aCurvedEdges ?
"yes" :
"no" ) );
2481 BOOST_CHECK_MESSAGE( !foundSelfIntersection,
2482 wxString::Format(
"Teardrop polygon has self-intersecting edges (curved=%s)",
2483 aCurvedEdges ?
"yes" :
"no" ) );
2487 runVariant(
false );
2511 auto runVariant = [&](
bool aCurvedEdges )
2518 for(
PCB_TRACK* track : m_board->Tracks() )
2524 via->SetTeardropCurved( aCurvedEdges );
2525 viaPos =
via->GetPosition();
2534 toolMgr.
SetEnvironment( m_board.get(),
nullptr,
nullptr,
nullptr,
nullptr );
2543 if( !commit.
Empty() )
2548 const double maxBackSideDist = viaRadius * 1.2;
2549 int teardropCount = 0;
2550 int spikingPoints = 0;
2552 double worstDistance = 0.0;
2554 for(
ZONE* zone : m_board->Zones() )
2556 if( !zone->IsTeardropArea() )
2568 for(
int i = 0; i <
chain.PointCount(); i++ )
2579 if( dist > maxBackSideDist )
2583 if( dist > worstDistance )
2585 worstDistance = dist;
2592 BOOST_CHECK_MESSAGE( teardropCount == 0,
2593 wxString::Format(
"Expected no teardrop on grazing-entry track (emergence below "
2594 "track width), got %d (curved=%s)",
2596 aCurvedEdges ?
"yes" :
"no" ) );
2598 BOOST_CHECK_MESSAGE( spikingPoints == 0,
2599 wxString::Format(
"Found %d teardrop polygon vertex/vertices outside the expected "
2600 "envelope (worst at (%d, %d), %f mm from via center; curved=%s)",
2602 worstPoint.
x, worstPoint.
y,
2604 aCurvedEdges ?
"yes" :
"no" ) );
2608 runVariant(
false );
2625 auto runVariant = [&](
bool aCurvedEdges )
2629 for(
PCB_TRACK* track : m_board->Tracks() )
2633 static_cast<PCB_VIA*
>( track )->SetTeardropCurved( aCurvedEdges );
2639 toolMgr.
SetEnvironment( m_board.get(),
nullptr,
nullptr,
nullptr,
nullptr );
2648 if( !commit.
Empty() )
2651 int teardropCount = 0;
2652 int selfIntersectingCount = 0;
2655 for(
ZONE* zone : m_board->Zones() )
2657 if( !zone->IsTeardropArea() )
2668 int n =
chain.PointCount();
2669 bool intersected =
false;
2671 for(
int i = 0; i < n && !intersected; i++ )
2673 SEG segA(
chain.CPoint( i ),
chain.CPoint( ( i + 1 ) % n ) );
2675 for(
int j = i + 2; j < n; j++ )
2677 if( i == 0 && j == n - 1 )
2680 SEG segB(
chain.CPoint( j ),
chain.CPoint( ( j + 1 ) % n ) );
2683 if( hit.has_value() )
2685 BOOST_TEST_MESSAGE( wxString::Format(
"Teardrop polygon self-intersection at (%d, %d) "
2686 "between edges %d and %d (curved=%s)",
2690 aCurvedEdges ?
"yes" :
"no" ) );
2692 worstPoint = hit.value();
2700 selfIntersectingCount++;
2703 BOOST_CHECK_MESSAGE( teardropCount > 0,
2704 wxString::Format(
"Expected at least one teardrop zone (curved=%s)",
2705 aCurvedEdges ?
"yes" :
"no" ) );
2707 BOOST_CHECK_MESSAGE( selfIntersectingCount == 0,
2708 wxString::Format(
"%d of %d teardrop polygon(s) self-intersect (worst at (%d, %d); "
2710 selfIntersectingCount,
2712 worstPoint.
x, worstPoint.
y,
2713 aCurvedEdges ?
"yes" :
"no" ) );
2717 runVariant(
false );
2725 auto runVariant = [&](
const wxString& aFixture,
bool aCurvedEdges )
2729 for(
PCB_TRACK* track : m_board->Tracks() )
2732 static_cast<PCB_VIA*
>( track )->SetTeardropCurved( aCurvedEdges );
2735 for(
FOOTPRINT* footprint : m_board->Footprints() )
2737 for(
PAD*
pad : footprint->Pads() )
2738 pad->SetTeardropCurved( aCurvedEdges );
2742 toolMgr.
SetEnvironment( m_board.get(),
nullptr,
nullptr,
nullptr,
nullptr );
2751 if( !commit.
Empty() )
2754 int teardropCount = 0;
2755 VECTOR2I padPos = ( *m_board->Footprints().begin() )->Pads()[0]->GetPosition();
2756 bool padHasTeardrop =
false;
2758 for(
ZONE* zone : m_board->Zones() )
2760 if( !zone->IsTeardropArea() )
2765 if( zone->Outline()->Contains( padPos ) )
2766 padHasTeardrop =
true;
2769 BOOST_CHECK_MESSAGE( padHasTeardrop,
2770 wxString::Format(
"Expected the pad-anchored teardrop on the short track joining the "
2771 "pad and the close via in %s, got %d teardrop(s) (curved=%s)",
2774 aCurvedEdges ?
"yes" :
"no" ) );
2777 runVariant(
"teardrop_close_via",
true );
2778 runVariant(
"teardrop_close_via",
false );
2779 runVariant(
"teardrop_close_via_rotated_pad",
true );
2780 runVariant(
"teardrop_close_via_rotated_pad",
false );
2800 ~ScopeGuard() { ref = orig; }
2803 auto getTotalFilledArea =
2808 for(
ZONE* zone : m_board->Zones() )
2810 if( zone->GetIsRuleArea() )
2815 if( !zone->HasFilledPolysForLayer( layer ) )
2818 std::shared_ptr<SHAPE_POLY_SET> fill = zone->GetFilledPolysList( layer );
2828 auto refillAndMeasure =
2829 [
this, &cfg, &getTotalFilledArea](
bool aIterative ) ->
double
2835 double storedArea = getTotalFilledArea();
2837 BOOST_REQUIRE_MESSAGE( storedArea > 0,
"Stored v9 fill has zero area" );
2840 return getTotalFilledArea();
2845 double storedArea = getTotalFilledArea();
2847 BOOST_REQUIRE_MESSAGE( storedArea > 0,
"Stored v9 fill has zero area" );
2849 double nonIterativeArea = refillAndMeasure(
false );
2850 double iterativeArea = refillAndMeasure(
true );
2851 double nonIterativeAreaRatio = nonIterativeArea / storedArea;
2852 double iterativeAreaRatio = iterativeArea / storedArea;
2854 BOOST_CHECK_MESSAGE( nonIterativeAreaRatio > 0.99999,
2855 wxString::Format(
"Non-iterative refill lost %.4f%% versus stored v9 fill "
2856 "(stored=%.2f mm^2, non-iterative=%.2f mm^2). This suggests "
2857 "missing pieces near keepout boundaries (issue 23515).",
2858 ( 1.0 - nonIterativeAreaRatio ) * 100.0,
2860 nonIterativeArea / 1e6 ) );
2862 BOOST_CHECK_MESSAGE( iterativeAreaRatio > 0.99999,
2863 wxString::Format(
"Iterative refill lost %.4f%% versus stored v9 fill "
2864 "(stored=%.2f mm^2, iterative=%.2f mm^2). "
2865 "This suggests missing pieces near keepout boundaries (issue 23515).",
2866 ( 1.0 - iterativeAreaRatio ) * 100.0,
2868 iterativeArea / 1e6 ) );
2882 m_board = std::make_unique<BOARD>();
2885 m_board->SetCopperLayerCount( 2 );
2894 m_board->Add( gndNet );
2906 int hatchThickness =
pcbIUScale.mmToIU( 0.3 );
2916 via->SetPosition( viaPos );
2918 via->SetDrill( viaDrill );
2920 via->SetNetCode( gndNetCode );
2921 m_board->Add(
via );
2925 auto makeHatchZone =
2928 ZONE* zone =
new ZONE( m_board.get() );
2947 m_board->Add( zone );
2954 m_board->BuildConnectivity();
2955 auto drcEngine = std::make_shared<DRC_ENGINE>( m_board.get(), &bds );
2956 drcEngine->InitEngine( wxFileName() );
2968 double fullFillArea = 0.0;
2969 double thermalFillArea = 0.0;
2980 "Zone should have fill on F.Cu with FULL connection" );
2984 for(
int i = 0; i < fill->OutlineCount(); i++ )
2985 fullFillArea +=
std::abs( fill->Outline( i ).Area() );
2987 m_board->Remove(
via );
2988 m_board->Remove( zone );
3002 "Zone should have fill on F.Cu with THERMAL connection" );
3006 for(
int i = 0; i < fill->OutlineCount(); i++ )
3007 thermalFillArea +=
std::abs( fill->Outline( i ).Area() );
3009 m_board->Remove(
via );
3010 m_board->Remove( zone );
3019 double areaThreshold = 0.2 * iuPerMM * iuPerMM;
3021 double areaIU2toMM2 = 1.0 / ( iuPerMM * iuPerMM );
3023 BOOST_CHECK_MESSAGE( thermalFillArea > fullFillArea + areaThreshold,
3024 wxString::Format(
"THERMAL connection fill area (%.2f sq mm) should be larger than "
3025 "FULL fill area (%.2f sq mm) by at least 0.2 sq mm. If they are "
3026 "equal or FULL is larger, thermal ring was not added for THERMAL "
3027 "connection, or thermal ring was incorrectly added for FULL connection "
3028 "(issue 23516 regression).",
3029 thermalFillArea * areaIU2toMM2,
3030 fullFillArea * areaIU2toMM2 ) );
3042 m_board = std::make_unique<BOARD>();
3043 m_board->SetCopperLayerCount( 2 );
3050 m_board->Add( gndNet );
3059 int hatchThickness =
pcbIUScale.mmToIU( 0.3 );
3061 auto makeHatchZone = [&]() ->
ZONE*
3063 ZONE* zone =
new ZONE( m_board.get() );
3080 m_board->Add( zone );
3086 const int steps = 8;
3087 const double startMM = 9.0;
3088 const double stepMM = 2.3 / steps;
3090 int isolatedCount = 0;
3091 int testedCount = 0;
3093 for(
int ix = 0; ix < steps; ix++ )
3095 for(
int iy = 0; iy < steps; iy++ )
3101 via->SetPosition( viaPos );
3103 via->SetDrill( viaDrill );
3105 via->SetNetCode( gndNetCode );
3106 m_board->Add(
via );
3108 ZONE* zone = makeHatchZone();
3110 m_board->BuildConnectivity();
3111 auto drcEngine = std::make_shared<DRC_ENGINE>( m_board.get(), &bds );
3112 drcEngine->InitEngine( wxFileName() );
3120 std::shared_ptr<SHAPE> viaShape =
via->GetEffectiveShape(
F_Cu );
3124 if( !fill->Collide( viaShape.get(), 0 ) )
3129 m_board->Remove(
via );
3130 m_board->Remove( zone );
3136 BOOST_CHECK_MESSAGE( isolatedCount == 0, wxString::Format(
"%d of %d FULL-connection via positions were left "
3137 "isolated from the hatch fill (issue 24559).",
3138 isolatedCount, testedCount ) );
3169 ~ScopeGuard() { ref = orig; }
3175 const std::vector<std::string> checkedNames = {
"hi1",
"hi2",
"hi3",
"hi4",
"hi5",
"hi6",
"hi7",
3176 "lo1",
"lo2",
"lo3",
"lo4",
"lo5",
"lo6" };
3177 std::map<std::string, ZONE*> zoneByName;
3179 for(
ZONE* zone : m_board->Zones() )
3180 zoneByName[zone->GetZoneName().ToStdString()] = zone;
3182 for(
const std::string&
name : checkedNames )
3184 BOOST_REQUIRE_MESSAGE( zoneByName.count(
name ),
"Zone '" +
name +
"' not found in test board" );
3185 BOOST_REQUIRE_MESSAGE( zoneByName[
name]->HasFilledPolysForLayer(
F_Cu ),
3186 "Zone '" +
name +
"' has no fill on F.Cu" );
3190 for(
const std::string&
name : {
"hi3",
"hi5",
"hi7" } )
3192 int islands = zoneByName[
name]->GetFilledPolysList(
F_Cu )->OutlineCount();
3194 BOOST_CHECK_MESSAGE( islands == 2, wxString::Format(
"Zone '%s' should have 2 filled islands but has %d. "
3195 "Cascading island removal did not converge correctly.",
3200 for(
const std::string&
name : {
"lo1",
"lo2",
"lo3",
"lo4",
"lo5",
"lo6",
"hi2",
"hi4",
"hi6" } )
3202 int islands = zoneByName[
name]->GetFilledPolysList(
F_Cu )->OutlineCount();
3204 BOOST_CHECK_MESSAGE( islands == 1, wxString::Format(
"Zone '%s' should have 1 filled island but has %d. "
3205 "Iterative refill may have incorrectly blocked or "
3206 "expanded this zone.",
3223 ZONE* thievingZone =
nullptr;
3225 for(
ZONE* z : m_board->Zones() )
3242 BOOST_CHECK_GE( fill->OutlineCount(), 2 );
3245 BOX2I fillBox = fill->BBox();
3252 BOOST_CHECK_GT( fill->TotalVertices(), 200 );
3278 ~ScopeGuard() { ref = orig; }
3287 BOOST_CHECK_MESSAGE( countingLog.
GetCount() > 0,
"Expected a wxLogWarning when iterative refill hits the iteration "
3288 "limit, but none was emitted. The convergence-limit board may no "
3289 "longer trigger the cap, or the warning path has changed." );
3300 m_board = std::make_unique<BOARD>();
3302 ZONE* zone =
new ZONE( m_board.get() );
3316 m_board->Add( zone );
3325 BOOST_CHECK_GT( fill->OutlineCount(), 5 );
3338 m_board = std::make_unique<BOARD>();
3339 m_board->SetCopperLayerCount( 2 );
3342 ZONE* zone =
new ZONE( m_board.get() );
3362 m_board->Add( zone );
3368 BOOST_REQUIRE_GT( fill->OutlineCount(), 0 );
3372 BOOST_CHECK_GE( fill->OutlineCount(), 6 );
3373 BOOST_CHECK_LE( fill->OutlineCount(), 12 );
3376 const double fullDotArea =
M_PI * std::pow(
pcbIUScale.mmToIU( 0.25 ), 2 );
3389 auto countDots = [](
bool stagger ) ->
int
3391 auto board = std::make_unique<BOARD>();
3392 board->SetCopperLayerCount( 2 );
3394 ZONE* zone =
new ZONE( board.get() );
3415 int plain = countDots(
false );
3416 int staggered = countDots(
true );
3422 BOOST_CHECK_NE( plain, staggered );
3423 BOOST_CHECK_GE( staggered, plain / 2 );
3424 BOOST_CHECK_LE( staggered, plain * 2 );
3435 m_board = std::make_unique<BOARD>();
3436 m_board->SetCopperLayerCount( 2 );
3438 ZONE* zone =
new ZONE( m_board.get() );
3452 m_board->Add( zone );
3458 BOOST_REQUIRE_GT( fill->OutlineCount(), 0 );
3462 BOOST_CHECK_GE( fill->OutlineCount(), 6 );
3463 BOOST_CHECK_LE( fill->OutlineCount(), 12 );
3465 const double fullSquareArea = std::pow(
pcbIUScale.mmToIU( 0.6 ), 2 );
3480 m_board = std::make_unique<BOARD>();
3481 m_board->SetCopperLayerCount( 2 );
3483 ZONE* zone =
new ZONE( m_board.get() );
3497 m_board->Add( zone );
3499 auto start = std::chrono::steady_clock::now();
3501 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
3502 std::chrono::steady_clock::now() - start )
3511#if !defined( KICAD_SANITIZE_THREADS ) && !defined( KICAD_SANITIZE_ADDRESS )
3514 BOOST_CHECK_LT( elapsed, 30000 );
3527 m_board = std::make_unique<BOARD>();
3528 m_board->SetCopperLayerCount( 2 );
3530 ZONE* zone =
new ZONE( m_board.get() );
3544 m_board->Add( zone );
3550 BOOST_REQUIRE_GT( fill->TotalVertices(), 0 );
3560 BOX2I fillBox = fill->BBox();
3568 BOOST_CHECK_GT( fill->TotalVertices(), 30 );
3588 for(
ZONE* zone : m_board->Zones() )
3590 if( zone->GetIsRuleArea() )
3595 if( !zone->HasFilledPolysForLayer( layer ) )
3598 std::shared_ptr<SHAPE_POLY_SET> fill = zone->GetFilledPolysList( layer );
3601 total += fill->OutlineCount();
3608 int storedIslands = countIslands();
3610 BOOST_REQUIRE_MESSAGE( storedIslands >= 3,
3611 wxString::Format(
"Stored v9 fill should have at least 3 silk islands; "
3617 int refilledIslands = countIslands();
3619 BOOST_CHECK_MESSAGE( refilledIslands == storedIslands,
3620 wxString::Format(
"Refill lost silk islands: stored=%d, refilled=%d. "
3621 "Outline 0 of every non-copper multi-island zone "
3622 "was being incorrectly removed (issue 24089).",
3623 storedIslands, refilledIslands ) );
3634 m_board = std::make_unique<BOARD>();
3635 m_board->SetCopperLayerCount( 4 );
3642 m_board->Add( gndNet );
3646 m_board->Add( vccNet );
3649 ZONE* gndZone =
new ZONE( m_board.get() );
3666 m_board->Add( gndZone );
3668 ZONE* vccZone =
new ZONE( m_board.get() );
3692 m_board->Add( vccZone );
3696 auto footprint = std::make_unique<FOOTPRINT>( m_board.get() );
3706 pad->SetNetCode( gndNetCode );
3708 footprint->Add(
pad );
3709 footprint->SetPosition(
VECTOR2I( 0, 0 ) );
3710 m_board->Add( footprint.release() );
3712 m_board->BuildConnectivity();
3713 auto drcEngine = std::make_shared<DRC_ENGINE>( m_board.get(), &bds );
3714 drcEngine->InitEngine( wxFileName() );
3722 "VCC outline must contain the pad position to reproduce issue 24175." );
3724 "VCC zone should still have fill in its right lobe." );
3729 BOOST_REQUIRE_MESSAGE( !vccFill->Contains(
pad->GetPosition() ),
3730 "VCC fill should NOT contain the pad position (corridor must be "
3731 "pruned by min-thickness for the test to exercise issue 24175)." );
3736 BOOST_CHECK_MESSAGE(
pad->FlashLayer(
In1_Cu ),
3737 "PTH pad inside higher-priority different-net zone must still flash "
3738 "when a same-net lower-priority zone covers it (issue 24175)." );
3741 "GND zone should have fill on In1.Cu" );
3748 bool foundCopperAround =
false;
3750 for(
int i = 0; i < samples; i++ )
3752 double angle = ( 2.0 *
M_PI * i ) / samples;
3754 pad->GetPosition().y +
KiROUND( sampleR * std::sin( angle ) ) );
3756 if( gndFill->Contains( p ) )
3758 foundCopperAround =
true;
3763 BOOST_CHECK_MESSAGE( foundCopperAround,
3764 "Lower-priority GND zone should have copper around GND pad even when "
3765 "a higher-priority different-net zone outline contains the pad "
3788 PCB_LAYER_ID targetLayer = m_board->GetLayerID( wxT(
"B.Cu" ) );
3789 std::vector<ZONE*> toFill;
3791 for(
ZONE* zone : m_board->Zones() )
3793 if( zone->GetIsRuleArea() )
3796 if( !zone->IsOnLayer( targetLayer ) )
3799 if( zone->IsFilled() )
3802 toFill.push_back( zone );
3805 BOOST_REQUIRE_MESSAGE( !toFill.empty(),
3806 "Expected at least one unfilled B.Cu zone to exercise the API path." );
3813 BOOST_CHECK_NO_THROW( filler.
Fill( toFill ) );
3832 ~ScopeGuard() { ref = orig; }
3841 const int margin =
pcbIUScale.mmToIU( 0.05 );
3843 std::map<int, SHAPE_POLY_SET> mergedByNet;
3845 for(
ZONE* zone : m_board->Zones() )
3847 if( zone->GetIsRuleArea() || !zone->HasFilledPolysForLayer( layer ) )
3850 mergedByNet[zone->GetNetCode()].BooleanAdd( *zone->GetFilledPolysList( layer ) );
3855 auto buildLegitVoids =
3861 for(
ZONE* other : m_board->Zones() )
3863 if( !other->GetLayerSet().Contains( layer ) )
3866 if( other->GetIsRuleArea() )
3868 if( other->GetDoNotAllowZoneFills() )
3874 if( other->GetNetCode() == aLower->
GetNetCode()
3876 || other->GetAssignedPriority() <= aHigher->GetAssignedPriority()
3877 || !other->HasFilledPolysForLayer( layer ) )
3890 std::vector<ZONE*> zones;
3892 for(
ZONE* zone : m_board->Zones() )
3894 if( !zone->GetIsRuleArea() && zone->GetNetCode() > 0 && zone->GetLayerSet().Contains( layer ) )
3895 zones.push_back( zone );
3898 int checkedPairs = 0;
3900 for(
size_t i = 0; i < zones.size(); ++i )
3902 for(
size_t j = i + 1; j < zones.size(); ++j )
3917 const ZONE* higher = ( lower == a ) ? b : a;
3930 double uncoveredArea =
3933 BOOST_CHECK_MESSAGE( uncoveredArea < 0.01,
3934 wxString::Format(
"Same-net zones (priorities %d and %d) left %.4f mm^2 of "
3935 "their overlap unfilled; overlapping same-net zones must "
3936 "merge (issue 23790).",
3943 BOOST_CHECK_MESSAGE( checkedPairs >= 2,
3944 wxString::Format(
"Expected at least two overlapping same-net zone pairs "
3945 "to exercise the merge, found %d.", checkedPairs ) );
3959 ~ScopeGuard() { ref = orig; }
3962 for(
bool iterative : {
true,
false } )
3970 ZONE* hatched =
nullptr;
3971 ZONE* solid =
nullptr;
3973 for(
ZONE* zone : m_board->Zones() )
4002 double leakedArea = leaked.
Area() / mm2;
4004 BOOST_CHECK_MESSAGE( leakedArea < 0.01,
4005 wxString::Format(
"%s: lower-priority zone poured %.3f mm^2 inside the "
4006 "higher-priority hatched zone (issue 24935).",
4007 iterative ? wxS(
"iterative refill" ) : wxS(
"single pass" ),
4027 ~ScopeGuard() { ref = orig; }
4036 for(
int pass = 0; pass < 64; ++pass )
4044 for(
ZONE* zone : m_board->Zones() )
4046 if( zone->GetIsRuleArea() || !zone->HasFilledPolysForLayer( layer ) )
4049 std::shared_ptr<SHAPE_POLY_SET> fill = zone->GetFilledPolysList( layer );
4055 BOOST_CHECK_MESSAGE( merged.
Area() > 0.0,
4056 wxString::Format(
"Fill pass %d produced no copper.", pass ) );
4070 auto outlineNoArcs =
4078 int checkedPairs = 0;
4080 for(
ZONE* hatch : m_board->Zones() )
4083 || !hatch->HasFilledPolysForLayer( layer ) )
4091 for(
ZONE* other : m_board->Zones() )
4093 if( other == hatch || other->GetIsRuleArea() || !other->GetLayerSet().Contains( layer )
4094 || other->GetAssignedPriority() <= hatch->GetAssignedPriority() )
4117 if( band.
Area() <= 0 )
4123 double coverage = covered.
Area() / band.
Area();
4126 BOOST_CHECK_MESSAGE( coverage >= 0.85,
4127 wxString::Format(
"Hatch zone %s carved by %s: border ring only %.0f%% filled; "
4128 "the hatch border was not re-established around the carved "
4129 "area (issue 24758).",
4130 hatch->GetZoneName(), other->GetZoneName(),
4131 coverage * 100.0 ) );
4135 BOOST_CHECK_MESSAGE( checkedPairs >= 3,
4136 wxString::Format(
"Expected at least three carved hatch borders to check, "
4137 "found %d.", checkedPairs ) );
4145 m_board = std::make_unique<BOARD>();
4156 m_board->Add( netA );
4157 m_board->Add( netB );
4163 const int sep =
clearance + extraMargin + maxError / 2;
4164 const int ax =
pcbIUScale.mmToIU( 100 ) + sep;
4166 ZONE* zoneA =
new ZONE( m_board.get() );
4174 m_board->Add( zoneA );
4178 ZONE* zoneB =
new ZONE( m_board.get() );
4187 for(
int ii = 0; ii < 1000; ++ii )
4195 m_board->Add( zoneB );
4207 BOOST_CHECK_MESSAGE( !fillA->Collide( fillB.get(),
clearance + extraMargin + maxError * 3 / 4 ),
4208 "Lower-priority zone filled before the higher-priority knockout was "
4209 "published; the fill depends on thread scheduling." );
4234 std::vector<DRC_ITEM> violations;
4237 [&](
const std::shared_ptr<DRC_ITEM>& aItem,
const VECTOR2I& aPos,
int aLayer,
4238 const std::function<
void(
PCB_MARKER* )>& aPathGenerator )
4241 violations.push_back( *aItem );
4246 if( !violations.empty() )
4252 for(
const DRC_ITEM& item : violations )
4256 BOOST_CHECK_MESSAGE( violations.empty(),
4257 wxString::Format(
"Zone fill produced %zu connection_width violations; "
4258 "expected 0 (issue 24312).",
4259 violations.size() ) );
4272 ~ScopeGuard() { ref = orig; }
4290 double localCopperArea = 0.0;
4292 for(
ZONE* zone : m_board->Zones() )
4294 if( !zone->IsOnLayer( layer ) )
4297 std::shared_ptr<SHAPE_POLY_SET> fill = zone->GetFilledPolysList( layer );
4310 localCopperArea = std::max( localCopperArea,
std::abs( local.
Area() ) );
4315 BOOST_CHECK_MESSAGE( localCopperArea > minimumLocalCopperArea,
4316 wxString::Format(
"Expected copper around the bridge on %s; the "
4317 "bridge must be widened, not removed.",
4321 std::vector<DRC_ITEM> violations;
4324 [&](
const std::shared_ptr<DRC_ITEM>& aItem,
const VECTOR2I&,
int,
4328 violations.push_back( *aItem );
4333 BOOST_CHECK_MESSAGE( violations.empty(),
4334 wxString::Format(
"Iterative refill produced %zu connection_width "
4335 "violations; expected full-width bridges (issue 24835).",
4336 violations.size() ) );
4351 ~ScopeGuard() { ref = orig; }
4357 [&](
bool aIterativeRefill, std::unique_ptr<BOARD>& aBoard )
4364 std::unique_ptr<BOARD> incremental;
4365 std::unique_ptr<BOARD> oneShot;
4367 fill(
true, incremental );
4368 fill(
false, oneShot );
4370 std::map<KIID, SHAPE_POLY_SET> incrementalFills;
4372 for(
ZONE* zone : incremental->Zones() )
4374 if( !zone->GetIsRuleArea() && zone->HasFilledPolysForLayer( layer ) )
4375 incrementalFills[zone->m_Uuid] = *zone->GetFilledPolysList( layer );
4381 auto sameNetBorderBand =
4391 for(
ZONE* other : oneShot->Zones() )
4393 if( other == aZone || other->GetIsRuleArea()
4394 || other->GetNetCode() != aZone->GetNetCode()
4395 || !other->GetLayerSet().Contains( layer ) )
4412 int checkedZones = 0;
4414 for(
ZONE* zone : oneShot->Zones() )
4416 if( zone->GetIsRuleArea() || zone->GetNetCode() <= 0
4417 || !zone->HasFilledPolysForLayer( layer ) )
4427 auto incrementalIt = incrementalFills.find( zone->m_Uuid );
4429 BOOST_REQUIRE_MESSAGE( incrementalIt != incrementalFills.end(),
4430 wxString::Format(
"Zone %s is missing from the incremental fill.",
4431 zone->m_Uuid.AsString() ) );
4434 difference.
BooleanXor( incrementalIt->second );
4437 const double differenceArea = difference.
Area() / sqMM;
4439 BOOST_CHECK_MESSAGE( differenceArea < 0.01,
4440 wxString::Format(
"Zone %s (priority %d) fills %.4f mm^2 differently along a "
4441 "shared same-net border depending on the fill mode; the two "
4442 "must agree there (issue 23790).",
4443 zone->m_Uuid.AsString(), zone->GetAssignedPriority(),
4448 BOOST_REQUIRE_MESSAGE( checkedZones >= 3,
4449 wxString::Format(
"Expected at least three zones sharing a same-net "
4450 "border, found %d.", checkedZones ) );
4463 for(
ZONE* zone : m_board->Zones() )
4465 if( !zone->GetIsRuleArea() )
4469 PAD* overriddenPad =
nullptr;
4470 ZONE* hostZone =
nullptr;
4473 for(
FOOTPRINT* footprint : m_board->Footprints() )
4475 for(
PAD*
pad : footprint->Pads() )
4477 for(
ZONE* zone : m_board->Zones() )
4479 if( zone->GetIsRuleArea() || zone->GetNetCode() !=
pad->GetNetCode() )
4484 if( !
pad->IsOnLayer( layer ) || !
pad->FlashLayer( layer ) )
4487 if( !zone->Outline()->Contains(
pad->GetPosition() ) )
4490 overriddenPad =
pad;
4508 BOOST_REQUIRE_MESSAGE( overriddenPad,
"No same-net pad sits inside a zone on this board." );
4516 BOOST_REQUIRE_MESSAGE( fill && !fill->IsEmpty(),
4517 wxString::Format(
"Zone on %s did not fill.",
4518 m_board->GetLayerName( hostLayer ) ) );
4521 std::shared_ptr<SHAPE> padShape = overriddenPad->
GetEffectiveShape( hostLayer );
4523 BOOST_CHECK_MESSAGE( fill->Collide( padShape.get(), 0 ),
4524 wxString::Format(
"Pad %s on net %s overrides the zone to a thermal "
4525 "connection, but the fill no longer reaches it on %s.",
4528 m_board->GetLayerName( hostLayer ) ) );
4542 ~ScopeGuard() { ref = orig; }
4550 std::vector<ZONE*> rails;
4552 for(
ZONE* zone : m_board->Zones() )
4554 if( zone->GetNetname() == wxS(
"gate_drv_rail" ) )
4555 rails.push_back( zone );
4558 BOOST_REQUIRE_EQUAL( rails.size(), 2 );
4561 const int reach = rails[0]->GetMinThickness();
4579 BOOST_CHECK_MESSAGE( uncovered < 0.01,
4580 wxString::Format(
"%.4f mm^2 unfilled at the shared edge", uncovered ) );
constexpr int ARC_HIGH_DEF
constexpr EDA_IU_SCALE pcbIUScale
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
virtual bool SetNetCode(int aNetCode, bool aNoAssert)
Set net using a net code.
wxString GetNetname() const
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
std::map< int, SEVERITY > m_DRCSeverities
std::shared_ptr< DRC_ENGINE > m_DRCEngine
void SetCopperLayerCount(int aNewLayerCount)
Set the copper layer count to aNewLayerCount.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Implements the RC_ITEM_RESOLVER interface to resolve PCB items by their KIID.
Information pertinent to a Pcbnew printed circuit board.
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
const ZONES & Zones() const
bool BuildConnectivity(PROGRESS_REPORTER *aReporter=nullptr)
Build or rebuild the board connectivity database for the board, especially the list of connected item...
BOARD_DESIGN_SETTINGS & GetDesignSettings()
constexpr coord_type GetLeft() const
constexpr coord_type GetRight() const
constexpr coord_type GetTop() const
constexpr coord_type GetBottom() const
const MINOPTMAX< int > & GetValue() const
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).
DRC_CONSTRAINT EvalRules(DRC_CONSTRAINT_T aConstraintType, const BOARD_ITEM *a, const BOARD_ITEM *b, PCB_LAYER_ID aLayer, REPORTER *aReporter=nullptr)
void ClearViolationHandler()
void InitEngine(const wxFileName &aRulePath)
Initialize the DRC engine.
virtual bool HitTest(const VECTOR2I &aPosition, int aAccuracy=0) const
Test if aPosition is inside or on the boundary of this item.
A scoped application of a wxLog target that counts error-level messages.
unsigned GetCount() const
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
static wxString Name(PCB_LAYER_ID aLayerId)
Return the fixed name association with aLayerId.
void SetClearance(int aClearance)
Handle the data for a net.
std::shared_ptr< NETCLASS > GetDefaultNetclass() const
Gets the default netclass for the project.
@ NORMAL
Shape is the same on all layers.
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
const wxString & GetNumber() const
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aMaxError, ERROR_LOC aErrorLoc=ERROR_INSIDE, bool aIgnoreLineWidth=false) const override
Convert the pad shape to a closed polygon.
VECTOR2I GetPosition() const override
void SetLocalZoneConnection(ZONE_CONNECTION aType)
VECTOR2I GetSize(PCB_LAYER_ID aLayer) const
std::shared_ptr< SHAPE > GetEffectiveShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, FLASHING aFlash=FLASHING::DEFAULT, DRC_CONSTRAINT_T aUsage=NULL_CONSTRAINT) const override
Some pad shapes can be complex (rounded/chamfered rectangle), even without considering custom shapes.
void SetEnd(const VECTOR2I &aEnd)
void SetStart(const VECTOR2I &aStart)
bool HitTest(const VECTOR2I &aPosition, int aAccuracy=0) const override
Test if aPosition is inside or on the boundary of this item.
virtual void SetWidth(int aWidth)
ecoord SquaredDistance(const SEG &aSeg) const
VECTOR2I::extended_type ecoord
int Length() const
Return the length (this).
OPT_VECTOR2I Intersect(const SEG &aSeg, bool aIgnoreEndpoints=false, bool aLines=false) const
Compute intersection point of segment (this) with segment aSeg.
VECTOR2I Center() const
Returns the center point of the line.
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 BooleanXor(const SHAPE_POLY_SET &b)
Perform boolean polyset exclusive or.
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.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
double Area()
Return the area of this poly set.
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
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)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
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.
void Deflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError)
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
int OutlineCount() const
Return the number of outlines in the set.
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.
SHAPE_POLY_SET CloneDropTriangulation() const
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
TEARDROP_MANAGER manage and build teardrop areas A teardrop area is a polygonal area (a copper ZONE) ...
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.
TEARDROP_PARAMETARS is a helper class to handle parameters needed to build teardrops for a board thes...
int m_TdMaxLen
max allowed length for teardrops in IU. <= 0 to disable
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
bool Fill(const std::vector< ZONE * > &aZones, bool aCheck=false, wxWindow *aParent=nullptr)
Fills the given list of zones.
Handle a list of polygons defining a copper zone.
void SetHatchThickness(int aThickness)
bool AppendCorner(const VECTOR2I &aPosition, int aHoleIdx, bool aAllowDuplication=false)
Add a new corner to the zone outline (to the main outline or a hole)
void AddPolygon(std::vector< VECTOR2I > &aPolygon)
Add a polygon to the zone outline.
std::shared_ptr< SHAPE_POLY_SET > GetFilledPolysList(PCB_LAYER_ID aLayer) const
void SetMinThickness(int aMinThickness)
double GetFilledArea()
This area is cached from the most recent call to CalculateFilledArea().
void SetThermalReliefSpokeWidth(int aThermalReliefSpokeWidth)
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
SHAPE_POLY_SET * Outline()
bool SetNetCode(int aNetCode, bool aNoAssert) override
Override that clamps the netcode to 0 when this zone is in copper-thieving fill mode.
void SetFillMode(ZONE_FILL_MODE aFillMode)
bool HasFilledPolysForLayer(PCB_LAYER_ID aLayer) const
void SetThievingSettings(const THIEVING_SETTINGS &aSettings)
void SetNet(NETINFO_ITEM *aNetInfo) override
Override that drops aNetInfo when this zone is in copper-thieving fill mode.
void SetThermalReliefGap(int aThermalReliefGap)
double CalculateFilledArea()
Compute the area currently occupied by the zone fill.
void SetAssignedPriority(unsigned aPriority)
void SetPadConnection(ZONE_CONNECTION aPadConnection)
void SetIslandRemovalMode(ISLAND_REMOVAL_MODE aRemove)
void SetHatchGap(int aStep)
unsigned GetAssignedPriority() const
bool SameNet(const ZONE *aOther) const
@ CHAMFER_ALL_CORNERS
All angles are chamfered.
@ ROUND_ALL_CORNERS
All angles are rounded.
static constexpr EDA_ANGLE ANGLE_0
bool m_ZoneFillIterativeRefill
Enable iterative zone filling to handle isolated islands in higher priority zones.
PCB_LAYER_ID
A quick note on layer IDs:
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)
@ PTH
Plated through hole pad.
@ REMOVE_EXCEPT_START_AND_END
std::optional< VECTOR2I > OPT_VECTOR2I
Parameters that drive copper-thieving fill generation.
EDA_ORIENTATION orientation
std::unique_ptr< BOARD > m_board
SETTINGS_MANAGER m_settingsManager
static PCB_VIA * makeVia(BOARD *aBoard, const VECTOR2I &aPos, VIATYPE aType, PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom, int aDrill)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
const SHAPE_LINE_CHAIN chain
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_CHECK_EQUAL(result, "25.4")
static PCB_TRACK * addCrossingTrack(BOARD &aBoard)
static const VECTOR2I crossedPosition(pcbIUScale.mmToIU(0.1), pcbIUScale.mmToIU(10))
static const std::vector< wxString > RegressionZoneFillTests_tests
static const std::vector< std::pair< wxString, int > > RegressionTeardropFill_tests
static void checkCrossingTrackTeardropsStayOnCopper(BOARD &aBoard, const PCB_TRACK *aTrack, const BOARD_ITEM *aCrossed)
BOOST_DATA_TEST_CASE_F(ZONE_FILL_TEST_FIXTURE, RegressionZoneFillTests, boost::unit_test::data::make(RegressionZoneFillTests_tests), relPath)
BOOST_AUTO_TEST_CASE(CrossingTrackTeardropOnViaStaysOnTrack)
static void CheckAllOutlineAreasAtLeast(const std::shared_ptr< SHAPE_POLY_SET > &aFill, double aMinArea, const wxString &aLabel)
Assert every outline in aFill has at least aMinArea — used to verify thieving stamps survived the fil...
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)
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
VECTOR2< int32_t > VECTOR2I
VECTOR2< double > VECTOR2D
ZONE_CONNECTION
How pads are covered by copper in zone.
@ THERMAL
Use thermal relief for pads.
@ FULL
pads are covered by copper