Ensure aperture mask DRC hits all tracks

First, we need to ensure that when DRC says it will report errors for
all track segments that this includes the aperature errors.  Second, max
clearance needs to include aperature clearance.  If you set that higher
than your standard clearance, the RTree might not catch elements that
don't have their own aperture clearance (like teardrops that inherit
from pads)

(cherry picked from commit c13b4e9757)
This commit is contained in:
Seth Hillbrand
2026-01-23 12:13:13 -08:00
parent 5e81b19ff0
commit 5a66715efd
8 changed files with 1976 additions and 18 deletions
+117 -18
View File
@@ -97,8 +97,14 @@ private:
// Shapes used to define solder mask apertures don't have nets, so we assign them the
// first object+net that bridges their aperture (after which any other nets will generate
// violations).
//
// When "report all track errors" is enabled, we store all items per net so we can report
// violations for each pair of items from different nets.
std::mutex m_netMapMutex;
std::unordered_map<PTR_LAYER_CACHE_KEY, std::pair<BOARD_ITEM*, int>> m_maskApertureNetMap;
// Extended storage for "report all track errors" mode: stores all items per net per aperture
std::unordered_map<PTR_LAYER_CACHE_KEY, std::vector<std::pair<BOARD_ITEM*, int>>> m_maskApertureNetMapAll;
};
@@ -338,12 +344,13 @@ bool DRC_TEST_PROVIDER_SOLDER_MASK::checkMaskAperture( BOARD_ITEM* aMaskItem, BO
int encounteredItemNet = -1;
{
std::lock_guard<std::mutex> lock( m_checkedPairsMutex );
std::lock_guard<std::mutex> lock( m_netMapMutex );
auto ii = m_maskApertureNetMap.find( key );
if( ii == m_maskApertureNetMap.end() )
{
m_maskApertureNetMap[ key ] = { aTestItem, aTestNet };
m_maskApertureNetMapAll[ key ].push_back( { aTestItem, aTestNet } );
// First net; no bridge yet....
return false;
@@ -351,12 +358,15 @@ bool DRC_TEST_PROVIDER_SOLDER_MASK::checkMaskAperture( BOARD_ITEM* aMaskItem, BO
alreadyEncounteredItem = ii->second.first;
encounteredItemNet = ii->second.second;
}
if( encounteredItemNet == aTestNet && aTestNet >= 0 )
{
// Same net; still no bridge...
return false;
if( encounteredItemNet == aTestNet && aTestNet >= 0 )
{
// Same net; still no bridge, but add this item to the list so we can report all
// pairwise violations later (for non-tracks always, for tracks when option is set).
m_maskApertureNetMapAll[ key ].push_back( { aTestItem, aTestNet } );
return false;
}
}
if( fp && aTestItem->GetParentFootprint() == fp )
@@ -593,24 +603,106 @@ void DRC_TEST_PROVIDER_SOLDER_MASK::testItemAgainstItems( BOARD_ITEM* aItem, con
{
if( checkMaskAperture( aItem, other, aRefLayer, otherNet, &colliding ) )
{
std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
PCB_LAYER_ID maskLayer = IsFrontLayer( aRefLayer ) ? F_Mask : B_Mask;
PTR_LAYER_CACHE_KEY key = { aItem, maskLayer };
std::vector<std::pair<BOARD_ITEM*, int>> itemsToReport;
bool reportedAnyTrack = false;
drce->SetErrorMessage( msg );
drce->SetItems( aItem, colliding, other );
drce->SetViolatingRule( &m_bridgeRule );
reportViolation( drce, pos, aTargetLayer );
{
std::lock_guard<std::mutex> lock( m_netMapMutex );
auto it = m_maskApertureNetMapAll.find( key );
if( it != m_maskApertureNetMapAll.end() )
itemsToReport = it->second;
}
if( !itemsToReport.empty() )
{
for( auto& [firstNetItem, firstNet] : itemsToReport )
{
// Always report all combinations for non-track items.
// For tracks, report all only when option is set; otherwise
// report just one track violation.
bool firstIsTrack = firstNetItem->Type() == PCB_TRACE_T
|| firstNetItem->Type() == PCB_ARC_T;
if( firstIsTrack )
{
if( m_drcEngine->GetReportAllTrackErrors() || !reportedAnyTrack )
{
auto drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
drce->SetErrorMessage( msg );
drce->SetItems( aItem, firstNetItem, other );
drce->SetViolatingRule( &m_bridgeRule );
reportViolation( drce, pos, aTargetLayer );
reportedAnyTrack = true;
}
}
else
{
auto drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
drce->SetErrorMessage( msg );
drce->SetItems( aItem, firstNetItem, other );
drce->SetViolatingRule( &m_bridgeRule );
reportViolation( drce, pos, aTargetLayer );
}
}
}
}
}
else if( isMaskAperture( other ) )
{
if( checkMaskAperture( other, aItem, aRefLayer, itemNet, &colliding ) )
{
std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
PCB_LAYER_ID maskLayer = IsFrontLayer( aRefLayer ) ? F_Mask : B_Mask;
PTR_LAYER_CACHE_KEY key = { other, maskLayer };
std::vector<std::pair<BOARD_ITEM*, int>> itemsToReport;
bool reportedAnyTrack = false;
drce->SetErrorMessage( msg );
drce->SetItems( other, colliding, aItem );
drce->SetViolatingRule( &m_bridgeRule );
reportViolation( drce, pos, aTargetLayer );
{
std::lock_guard<std::mutex> lock( m_netMapMutex );
auto it = m_maskApertureNetMapAll.find( key );
if( it != m_maskApertureNetMapAll.end() )
itemsToReport = it->second;
}
if( !itemsToReport.empty() )
{
for( auto& [firstNetItem, firstNet] : itemsToReport )
{
// Always report all combinations for non-track items.
// For tracks, report all only when option is set; otherwise
// report just one track violation.
bool firstIsTrack = firstNetItem->Type() == PCB_TRACE_T
|| firstNetItem->Type() == PCB_ARC_T;
if( firstIsTrack )
{
if( m_drcEngine->GetReportAllTrackErrors() || !reportedAnyTrack )
{
auto drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
drce->SetErrorMessage( msg );
drce->SetItems( other, firstNetItem, aItem );
drce->SetViolatingRule( &m_bridgeRule );
reportViolation( drce, pos, aTargetLayer );
reportedAnyTrack = true;
}
}
else
{
auto drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
drce->SetErrorMessage( msg );
drce->SetItems( other, firstNetItem, aItem );
drce->SetViolatingRule( &m_bridgeRule );
reportViolation( drce, pos, aTargetLayer );
}
}
}
}
}
else if( checkItemMask( other, itemNet ) )
@@ -825,10 +917,16 @@ bool DRC_TEST_PROVIDER_SOLDER_MASK::Run()
updateLargestClearance( static_cast<PCB_SHAPE*>( item )->GetSolderMaskExpansion() );
}
// Order is important here: m_webWidth must be added in before m_largestCourtyardClearance is
// maxed with the various SILK_CLEARANCE_CONSTRAINTS.
// Order is important here: m_webWidth must be added in before m_largestClearance is
// maxed with the various clearance constraints.
m_largestClearance += m_largestClearance + m_webWidth;
// Include SolderMaskToCopperClearance so R-tree queries find copper items that are within
// the required distance of mask apertures. Without this, tracks passing near pad apertures
// from different nets would not be found if SolderMaskToCopperClearance > m_largestClearance.
m_largestClearance = std::max( m_largestClearance,
m_board->GetDesignSettings().m_SolderMaskToCopperClearance );
DRC_CONSTRAINT worstClearanceConstraint;
if( m_drcEngine->QueryWorstConstraint( SILK_CLEARANCE_CONSTRAINT, worstClearanceConstraint ) )
@@ -839,6 +937,7 @@ bool DRC_TEST_PROVIDER_SOLDER_MASK::Run()
m_checkedPairs.clear();
m_maskApertureNetMap.clear();
m_maskApertureNetMapAll.clear();
buildRTrees();
@@ -0,0 +1,494 @@
(kicad_pcb
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
(general
(thickness 1.6)
(legacy_teardrops no)
)
(paper "A4")
(layers
(0 "F.Cu" signal)
(2 "B.Cu" signal)
(9 "F.Adhes" user "F.Adhesive")
(11 "B.Adhes" user "B.Adhesive")
(13 "F.Paste" user)
(15 "B.Paste" user)
(5 "F.SilkS" user "F.Silkscreen")
(7 "B.SilkS" user "B.Silkscreen")
(1 "F.Mask" user)
(3 "B.Mask" user)
(17 "Dwgs.User" user "User.Drawings")
(19 "Cmts.User" user "User.Comments")
(21 "Eco1.User" user "User.Eco1")
(23 "Eco2.User" user "User.Eco2")
(25 "Edge.Cuts" user)
(27 "Margin" user)
(31 "F.CrtYd" user "F.Courtyard")
(29 "B.CrtYd" user "B.Courtyard")
(35 "F.Fab" user)
(33 "B.Fab" user)
(39 "User.1" user)
(41 "User.2" user)
(43 "User.3" user)
(45 "User.4" user)
)
(setup
(pad_to_mask_clearance 0)
(allow_soldermask_bridges_in_footprints no)
(tenting front back)
(pcbplotparams
(layerselection 0x00000000_00000000_55555555_5755f5ff)
(plot_on_all_layers_selection 0x00000000_00000000_00000000_00000000)
(disableapertmacros no)
(usegerberextensions no)
(usegerberattributes yes)
(usegerberadvancedattributes yes)
(creategerberjobfile yes)
(dashed_line_dash_ratio 12.000000)
(dashed_line_gap_ratio 3.000000)
(svgprecision 4)
(plotframeref no)
(mode 1)
(useauxorigin no)
(hpglpennumber 1)
(hpglpenspeed 20)
(hpglpendiameter 15.000000)
(pdf_front_fp_property_popups yes)
(pdf_back_fp_property_popups yes)
(pdf_metadata yes)
(pdf_single_document no)
(dxfpolygonmode yes)
(dxfimperialunits yes)
(dxfusepcbnewfont yes)
(psnegative no)
(psa4output no)
(plot_black_and_white yes)
(sketchpadsonfab no)
(plotpadnumbers no)
(hidednponfab no)
(sketchdnponfab yes)
(crossoutdnponfab yes)
(subtractmaskfromsilk no)
(outputformat 1)
(mirror no)
(drillshape 1)
(scaleselection 1)
(outputdirectory "")
)
)
(net 0 "")
(net 1 "a")
(net 2 "b")
(footprint "Diode_THT:D_DO-247_Horizontal_TabDown"
(layer "F.Cu")
(uuid "971fa951-d58e-4bc8-a611-d70210c90fdb")
(at 193 59)
(descr "Diode, DO-247 series, Horizontal, TabDown, P 10.9mm")
(tags "diode rectifier")
(property "Reference" "REF**"
(at 5.45 -27.15 0)
(layer "F.SilkS")
(uuid "69e8b83a-a18a-4019-9510-efc8d3af1181")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Value" "D_DO-247_Horizontal_TabDown"
(at 5.45 3.25 0)
(layer "F.Fab")
(uuid "971ad017-97b6-48dd-b0c2-194c7704bb15")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "2dbd617d-da63-4cf5-9e2a-874b00aa8703")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "5d166361-c5b7-42ae-a1ca-87ed9d397eb2")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(attr through_hole)
(fp_line
(start -2.62 -26.15)
(end -2.62 -4.96)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "c4d62f0c-6fc0-4dcf-a2c9-fe06fd47d17a")
)
(fp_line
(start -2.62 -26.15)
(end 13.52 -26.15)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "7ea5aa52-0e4b-4173-8528-6594ff41b56f")
)
(fp_line
(start -2.62 -4.96)
(end 13.52 -4.96)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "172f5657-2113-4425-b917-9d7ebdf77cd6")
)
(fp_line
(start 0 -4.96)
(end 0 -2.4)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "42863e0b-8ca5-49a0-a75a-f0116870e4d0")
)
(fp_line
(start 4 -2)
(end 4 2)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "ac8db9d5-57bd-400b-8bda-fdb5edc4936e")
)
(fp_line
(start 4 0)
(end 2.5 0)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "1a7200a3-e719-496d-bae5-a9102bf5c1ff")
)
(fp_line
(start 4 0)
(end 7 -2)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "bbe77901-6bd4-41ed-b18a-9575af40a908")
)
(fp_line
(start 7 -2)
(end 7 2)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "6b7ef3ea-35b4-4bfe-80b8-d9092e529119")
)
(fp_line
(start 7 0)
(end 8.5 0)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "e2c40f56-3085-4586-8366-225beb4e632e")
)
(fp_line
(start 7 2)
(end 4 0)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "80c34c54-4634-4022-83c3-d4c5e0d60721")
)
(fp_line
(start 10.9 -4.96)
(end 10.9 -2.4)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "b4a19923-cf34-4f8c-b522-f1920c0000c2")
)
(fp_line
(start 13.52 -26.15)
(end 13.52 -4.96)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "7777916c-2224-489b-a07b-f4d824e6fd73")
)
(fp_line
(start -2.75 -26.28)
(end -2.75 2.5)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "6f175f83-0773-4d39-8059-5aa81041cae0")
)
(fp_line
(start -2.75 2.5)
(end 13.65 2.5)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "52abf367-bc48-4d1f-95b2-0df23c2a75db")
)
(fp_line
(start 13.65 -26.28)
(end -2.75 -26.28)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "553c8292-61e7-45e4-a29f-23d47cbd5715")
)
(fp_line
(start 13.65 2.5)
(end 13.65 -26.28)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "db38a51c-e5c8-405a-a929-6d72d9c116b7")
)
(fp_line
(start -2.5 -26.03)
(end 13.4 -26.03)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "26532d99-a1b6-4f84-a2e4-4d94ea49778d")
)
(fp_line
(start -2.5 -5.08)
(end -2.5 -26.03)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "4005a834-d0ae-4d2a-ab6d-d4a7a43d98c6")
)
(fp_line
(start 0 -5.08)
(end 0 0)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "59b9c817-6fde-4b2b-bb34-0c8ac031f799")
)
(fp_line
(start 10.9 -5.08)
(end 10.9 0)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "6138197f-dd9c-4754-895f-8663cc8a09a9")
)
(fp_line
(start 13.4 -26.03)
(end 13.4 -5.08)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "4906d6b3-80d5-43a4-a4e9-dadc9d92b909")
)
(fp_line
(start 13.4 -5.08)
(end -2.5 -5.08)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "33b114cb-9df0-4554-9b98-859bfa9e6e88")
)
(fp_circle
(center 5.45 -19.86)
(end 7.255 -19.86)
(stroke
(width 0.1)
(type solid)
)
(fill no)
(layer "F.Fab")
(uuid "fa956f5c-23b4-4dcb-a8fe-0e0db1f25ab1")
)
(fp_text user "${REFERENCE}"
(at 5.45 -27.15 0)
(layer "F.Fab")
(uuid "8c3ea185-51a7-4385-a58e-63f88218ca0d")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(pad "" np_thru_hole oval
(at 5.45 -19.86)
(size 3.6 3.6)
(drill 3.6)
(layers "*.Cu" "*.Mask")
(uuid "b51b6f62-3220-4f4d-90e0-8a7c41b6727a")
)
(pad "1" thru_hole rect
(at 0 0)
(size 2.5 4.5)
(drill 1.5)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(net 1 "a")
(uuid "5ffcbe15-06fd-4537-a9ad-5a0150cab53e")
)
(pad "2" thru_hole oval
(at 10.9 0)
(size 2.5 4.5)
(drill 1.5)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(net 2 "b")
(uuid "60096c5f-1518-45c1-84c6-56fbc3c2b033")
)
(embedded_fonts no)
(model "${KICAD9_3DMODEL_DIR}/Diode_THT.3dshapes/D_DO-247_Horizontal_TabDown.step"
(offset
(xyz 0 0 0)
)
(scale
(xyz 1 1 1)
)
(rotate
(xyz 0 0 0)
)
)
)
(gr_rect
(start 191 60)
(end 205 81)
(stroke
(width 0.1)
(type default)
)
(fill yes)
(layer "F.Mask")
(uuid "4515b5a6-f517-4096-b718-e6099702aebd")
)
(segment
(start 197 66)
(end 198 67)
(width 0.2)
(layer "F.Cu")
(net 1)
(uuid "2b83570d-6580-43c6-826a-b24b5c910270")
)
(segment
(start 193 62)
(end 197 66)
(width 0.2)
(layer "F.Cu")
(net 1)
(uuid "5173d5c5-567f-4e68-a64d-53d8c68cf218")
)
(segment
(start 193 59)
(end 193 62)
(width 0.2)
(layer "F.Cu")
(net 1)
(uuid "a38aeae3-381f-472a-b729-f6cb65dde676")
)
(segment
(start 198 67)
(end 198 80)
(width 0.2)
(layer "F.Cu")
(net 1)
(uuid "b9c9b074-47d6-49f1-9bd3-2ac72b7ac9cf")
)
(segment
(start 201 65)
(end 199 67)
(width 0.2)
(layer "F.Cu")
(net 2)
(uuid "1d9d967f-1c84-44f9-9e79-79599d939906")
)
(segment
(start 203.9 62.1)
(end 201 65)
(width 0.2)
(layer "F.Cu")
(net 2)
(uuid "25704320-724b-4996-8cb7-38e5a448aaa1")
)
(segment
(start 199 67)
(end 199 80)
(width 0.2)
(layer "F.Cu")
(net 2)
(uuid "37a4bd9a-91cf-42a9-aaa9-ef44d65e6333")
)
(segment
(start 203.9 59)
(end 203.9 62.1)
(width 0.2)
(layer "F.Cu")
(net 2)
(uuid "daaab621-c991-425c-8d9e-0aaf32a89efa")
)
(embedded_fonts no)
)
@@ -0,0 +1,614 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 1.5,
"height": 4.5,
"width": 2.5
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "soldermask_test.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "Default Editing",
"sort_asc": true,
"sort_field": "Reference"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"space_save_all_events": true,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [],
"text_variables": {}
}
@@ -0,0 +1,131 @@
(kicad_pcb
(version 20240108)
(generator "pcbnew")
(generator_version "8.0")
(general
(thickness 1.6)
(legacy_teardrops no)
)
(paper "A4")
(layers
(0 "F.Cu" signal)
(31 "B.Cu" signal)
(32 "B.Adhes" user "B.Adhesive")
(33 "F.Adhes" user "F.Adhesive")
(34 "B.Paste" user)
(35 "F.Paste" user)
(36 "B.SilkS" user "B.Silkscreen")
(37 "F.SilkS" user "F.Silkscreen")
(38 "B.Mask" user)
(39 "F.Mask" user)
(40 "Dwgs.User" user "User.Drawings")
(41 "Cmts.User" user "User.Comments")
(42 "Eco1.User" user "User.Eco1")
(43 "Eco2.User" user "User.Eco2")
(44 "Edge.Cuts" user)
(45 "Margin" user)
(46 "B.CrtYd" user "B.Courtyard")
(47 "F.CrtYd" user "F.Courtyard")
(48 "B.Fab" user)
(49 "F.Fab" user)
(50 "User.1" user)
(51 "User.2" user)
(52 "User.3" user)
(53 "User.4" user)
(54 "User.5" user)
(55 "User.6" user)
(56 "User.7" user)
(57 "User.8" user)
(58 "User.9" user)
)
(setup
(pad_to_mask_clearance 0.05)
(allow_soldermask_bridges_in_footprints no)
(pcbplotparams
(layerselection 0x00010fc_ffffffff)
(plot_on_all_layers_selection 0x0000000_00000000)
(disableapertmacros no)
(usegerberextensions no)
(usegerberattributes yes)
(usegerberadvancedattributes yes)
(creategerberjobfile yes)
(dashed_line_dash_ratio 12.000000)
(dashed_line_gap_ratio 3.000000)
(svgprecision 4)
(plotframeref no)
(viasonmask no)
(mode 1)
(useauxorigin no)
(hpglpennumber 1)
(hpglpenspeed 20)
(hpglpendiameter 15.000000)
(pdf_front_fp_property_popups yes)
(pdf_back_fp_property_popups yes)
(pdf_metadata yes)
(dxfpolygonmode yes)
(dxfimperialunits yes)
(dxfusepcbnewfont yes)
(psnegative no)
(psa4output no)
(plotreference yes)
(plotvalue yes)
(plotfptext yes)
(plotinvisibletext no)
(sketchpadsonfab no)
(subtractmaskfromsilk no)
(outputformat 1)
(mirror no)
(drillshape 1)
(scaleselection 1)
(outputdirectory "")
)
)
(net 0 "")
(net 1 "netA")
(net 2 "netB")
(footprint "TestPad"
(layer "B.Cu")
(uuid "11111111-1111-1111-1111-111111111111")
(at 100 100)
(property "Reference" "P1"
(at 0 -2 0)
(layer "B.SilkS")
(uuid "22222222-2222-2222-2222-222222222222")
(effects
(font
(size 1 1)
(thickness 0.15)
)
(justify mirror)
)
)
(property "Value" "TestPad"
(at 0 2 0)
(layer "B.Fab")
(uuid "33333333-3333-3333-3333-333333333333")
(effects
(font
(size 1 1)
(thickness 0.15)
)
(justify mirror)
)
)
(pad "1" smd roundrect
(at 0 0)
(size 1 1)
(layers "B.Cu" "B.Paste" "B.Mask")
(roundrect_rratio 0.25)
(net 1 "netA")
(uuid "44444444-4444-4444-4444-444444444444")
)
)
(segment
(start 100.6 95)
(end 100.6 105)
(width 0.2)
(layer "B.Cu")
(net 2)
(uuid "55555555-5555-5555-5555-555555555555")
)
)
@@ -0,0 +1,284 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.1,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.8,
"height": 1.27,
"width": 2.54
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 1.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "soldermask_track_to_pad.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"legacy_lib_dir": "",
"legacy_lib_list": []
},
"sheets": [],
"text_variables": {}
}
+2
View File
@@ -77,6 +77,8 @@ set( QA_PCBNEW_SRCS
drc/test_drc_copper_graphics.cpp
drc/test_drc_copper_sliver.cpp
drc/test_solder_mask_bridging.cpp
drc/test_solder_mask_bridge_all_tracks.cpp
drc/test_solder_mask_track_to_pad.cpp
drc/test_drc_multi_netclasses.cpp
drc/test_drc_skew.cpp
drc/test_drc_component_classes.cpp
@@ -0,0 +1,192 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
/**
* @file test_solder_mask_bridge_all_tracks.cpp
* Test that soldermask bridging reports all track combinations when "report all track errors"
* is enabled.
*
* Bug description: When DRC checks for soldermask bridging between two nets, only one object
* from net A is reported against all objects from net B. With "report all track errors" enabled,
* all track combinations should be reported.
*/
#include <qa_utils/wx_utils/unit_test_utils.h>
#include <pcbnew_utils/board_test_utils.h>
#include <board.h>
#include <board_design_settings.h>
#include <pcb_marker.h>
#include <pcb_track.h>
#include <drc/drc_item.h>
#include <settings/settings_manager.h>
#include <set>
struct DRC_SOLDER_MASK_BRIDGE_ALL_TRACKS_FIXTURE
{
DRC_SOLDER_MASK_BRIDGE_ALL_TRACKS_FIXTURE()
{ }
SETTINGS_MANAGER m_settingsManager;
std::unique_ptr<BOARD> m_board;
};
BOOST_FIXTURE_TEST_CASE( DRCSolderMaskBridgeAllTracksTest, DRC_SOLDER_MASK_BRIDGE_ALL_TRACKS_FIXTURE )
{
// Test board has:
// - Net "a" with 4 track segments
// - Net "b" with 4 track segments
// - A soldermask opening (gr_rect on F.Mask) covering where tracks from both nets pass through
//
// With "report all track errors" enabled, we expect all combinations of net A tracks vs net B
// tracks to be reported. Each track from net A that bridges with each track from net B should
// generate a violation.
wxString brd_name( wxT( "soldermask_bridge_all_tracks" ) );
KI_TEST::LoadBoard( m_settingsManager, brd_name, m_board );
std::vector<DRC_ITEM> violations;
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
// Disable DRC tests not relevant to this test
bds.m_DRCSeverities[ DRCE_LIB_FOOTPRINT_ISSUES ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_LIB_FOOTPRINT_MISMATCH ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_COPPER_SLIVER ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_STARVED_THERMAL ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_SILK_CLEARANCE ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCEngine->SetViolationHandler(
[&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
const std::function<void( PCB_MARKER* )>& aPathGenerator )
{
if( aItem->GetErrorCode() == DRCE_SOLDERMASK_BRIDGE )
violations.push_back( *aItem );
} );
// Count tracks per net to understand expected violations
int netA_tracks = 0;
int netB_tracks = 0;
for( PCB_TRACK* track : m_board->Tracks() )
{
if( track->GetNetname() == wxT( "a" ) )
netA_tracks++;
else if( track->GetNetname() == wxT( "b" ) )
netB_tracks++;
}
BOOST_TEST_MESSAGE( wxString::Format( "Net A has %d tracks, Net B has %d tracks",
netA_tracks, netB_tracks ) );
// Run DRC with "report all track errors" = true
bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true /* aReportAllTrackErrors */, false );
// With "report all track errors" enabled, we expect all track pair combinations to be reported.
// In the test board, both nets have 4 track segments each that pass through the soldermask
// opening. The soldermask aperture (gr_rect) bridges copper from different nets.
//
// Each track from net A that is close enough to each track from net B within the soldermask
// opening should generate a violation. For this test case, we expect multiple violations,
// not just one.
//
// The exact number depends on which track segments are actually within clearance distance
// in the soldermask opening area.
// We expect MORE violations when reporting all track errors than in single error mode.
// With 4 tracks + 1 pad on each net passing through the soldermask opening:
// - Non-track items (pads) always get all combinations reported
// - Track items get all combinations only when "report all track errors" is enabled
//
// The original bug was that all violations showed the same track from net A (the first
// one cached), regardless of how many tracks actually violated. Now:
// - All non-track items from net A are reported against each net B collision
// - All track items from net A are reported (when option is set) against each net B collision
BOOST_CHECK_GT( violations.size(), 5 );
BOOST_TEST_MESSAGE( wxString::Format( "Found %zu soldermask bridge violations",
violations.size() ) );
// Verify that we're reporting different items from net A (not just the first cached one)
std::set<KIID> netAItemIds;
for( const DRC_ITEM& item : violations )
{
if( item.GetAuxItemID() != niluuid )
netAItemIds.insert( item.GetAuxItemID() );
}
// With 4 tracks + 1 pad on net A, we should see multiple different items reported
BOOST_CHECK_GE( netAItemIds.size(), 4 );
BOOST_TEST_MESSAGE( wxString::Format( "Found %zu different net A items in violations",
netAItemIds.size() ) );
}
BOOST_FIXTURE_TEST_CASE( DRCSolderMaskBridgeSingleErrorTest, DRC_SOLDER_MASK_BRIDGE_ALL_TRACKS_FIXTURE )
{
// When "report all track errors" is disabled, we should get fewer violations
// (the original behavior - only report one error per track connection)
wxString brd_name( wxT( "soldermask_bridge_all_tracks" ) );
KI_TEST::LoadBoard( m_settingsManager, brd_name, m_board );
std::vector<DRC_ITEM> violations;
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
bds.m_DRCSeverities[ DRCE_LIB_FOOTPRINT_ISSUES ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_LIB_FOOTPRINT_MISMATCH ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_COPPER_SLIVER ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_STARVED_THERMAL ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_SILK_CLEARANCE ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCEngine->SetViolationHandler(
[&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
const std::function<void( PCB_MARKER* )>& aPathGenerator )
{
if( aItem->GetErrorCode() == DRCE_SOLDERMASK_BRIDGE )
violations.push_back( *aItem );
} );
// Run DRC with "report all track errors" = false
bds.m_DRCEngine->RunTests( EDA_UNITS::MM, false /* aReportAllTrackErrors */, false );
size_t singleErrorViolations = violations.size();
BOOST_TEST_MESSAGE( wxString::Format( "Single error mode: Found %zu soldermask bridge violations",
singleErrorViolations ) );
// Run again with "report all track errors" = true for comparison
violations.clear();
bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true /* aReportAllTrackErrors */, false );
size_t allErrorsViolations = violations.size();
BOOST_TEST_MESSAGE( wxString::Format( "All errors mode: Found %zu soldermask bridge violations",
allErrorsViolations ) );
// When reporting all errors, we should have at least as many (likely more) violations
BOOST_CHECK_GE( allErrorsViolations, singleErrorViolations );
}
@@ -0,0 +1,142 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
/**
* @file test_solder_mask_track_to_pad.cpp
* Test soldermask bridging detection between tracks and pads from different nets.
*
* Bug description: When a track passes near a pad from a different net, and that track
* is within the pad's soldermask aperture + SolderMaskToCopperClearance, a bridging error
* should be reported. This was not happening because the R-tree query clearance did not
* include SolderMaskToCopperClearance.
*/
#include <qa_utils/wx_utils/unit_test_utils.h>
#include <pcbnew_utils/board_test_utils.h>
#include <board.h>
#include <board_design_settings.h>
#include <pcb_track.h>
#include <drc/drc_item.h>
#include <settings/settings_manager.h>
#include <kiid.h>
struct DRC_TRACK_TO_PAD_BRIDGE_FIXTURE
{
DRC_TRACK_TO_PAD_BRIDGE_FIXTURE()
{ }
SETTINGS_MANAGER m_settingsManager;
std::unique_ptr<BOARD> m_board;
};
BOOST_FIXTURE_TEST_CASE( DRCTrackToPadBridgeTest, DRC_TRACK_TO_PAD_BRIDGE_FIXTURE )
{
// Test board has:
// - A pad on B.Cu with B.Mask at position (100, 100) on net "netA"
// - A track on B.Cu at x=100.6 (0.6mm from pad center) on net "netB"
// - SolderMaskToCopperClearance set to 1.0mm
//
// The track is 0.6mm from the pad center. With a 1mm pad and 0.05mm mask expansion,
// the pad edge is at 100.5mm and mask aperture edge is at 100.55mm.
// The track (0.2mm wide) has its nearest edge at 100.5mm.
//
// The track copper is within the SolderMaskToCopperClearance of the pad's mask aperture,
// so a violation should be reported.
wxString brd_name( wxT( "soldermask_track_to_pad" ) );
KI_TEST::LoadBoard( m_settingsManager, brd_name, m_board );
std::vector<DRC_ITEM> violations;
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
// Verify the clearance is set
BOOST_TEST_MESSAGE( wxString::Format( "SolderMaskToCopperClearance: %d nm",
bds.m_SolderMaskToCopperClearance ) );
// Disable DRC tests not relevant to this test
bds.m_DRCSeverities[ DRCE_LIB_FOOTPRINT_ISSUES ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_LIB_FOOTPRINT_MISMATCH ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_COPPER_SLIVER ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_STARVED_THERMAL ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[ DRCE_SILK_CLEARANCE ] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCEngine->SetViolationHandler(
[&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
const std::function<void( PCB_MARKER* )>& aPathGenerator )
{
if( aItem->GetErrorCode() == DRCE_SOLDERMASK_BRIDGE )
violations.push_back( *aItem );
} );
// Run DRC
bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true /* aReportAllTrackErrors */, false );
BOOST_TEST_MESSAGE( wxString::Format( "Found %zu soldermask bridge violations",
violations.size() ) );
// We expect at least one violation: track from netB is within clearance of pad from netA
BOOST_CHECK_GE( violations.size(), 1 );
// Verify the violation involves the track by checking item IDs in the cache
bool foundTrackViolation = false;
const auto& itemCache = m_board->GetItemByIdCache();
for( const DRC_ITEM& item : violations )
{
KIID mainId = item.GetMainItemID();
KIID auxId = item.GetAuxItemID();
BOARD_ITEM* mainItem = nullptr;
BOARD_ITEM* auxItem = nullptr;
if( mainId != niluuid )
{
auto it = itemCache.find( mainId );
if( it != itemCache.end() )
mainItem = it->second;
}
if( auxId != niluuid )
{
auto it = itemCache.find( auxId );
if( it != itemCache.end() )
auxItem = it->second;
}
BOOST_TEST_MESSAGE( wxString::Format( "Violation: main=%s aux=%s",
mainItem ? mainItem->GetClass() : "null",
auxItem ? auxItem->GetClass() : "null" ) );
if( ( mainItem && mainItem->Type() == PCB_TRACE_T ) ||
( auxItem && auxItem->Type() == PCB_TRACE_T ) )
{
foundTrackViolation = true;
}
}
BOOST_CHECK_MESSAGE( foundTrackViolation, "Expected to find a track-to-pad soldermask violation" );
}