diff --git a/pcbnew/drc/drc_engine.cpp b/pcbnew/drc/drc_engine.cpp index 354f06fa80..648e409beb 100644 --- a/pcbnew/drc/drc_engine.cpp +++ b/pcbnew/drc/drc_engine.cpp @@ -782,7 +782,8 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO // Local overrides take precedence over everything *except* board min clearance if( aConstraintType == CLEARANCE_CONSTRAINT || aConstraintType == HOLE_CLEARANCE_CONSTRAINT ) { - int override = 0; + int override_val = 0; + wxString msg; if( ac && !b_is_non_copper ) { @@ -795,7 +796,7 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO EscapeHTML( a->GetSelectMenuText( UNITS ) ), REPORT_VALUE( overrideA ) ) ) - override = ac->GetLocalClearanceOverrides( &m_msg ); + override_val = ac->GetLocalClearanceOverrides( &msg ); } } @@ -810,40 +811,40 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO EscapeHTML( b->GetSelectMenuText( UNITS ) ), EscapeHTML( REPORT_VALUE( overrideB ) ) ) ) - if( overrideB > override ) - override = bc->GetLocalClearanceOverrides( &m_msg ); + if( overrideB > override_val ) + override_val = bc->GetLocalClearanceOverrides( &msg ); } } - if( override ) + if( override_val ) { if( aConstraintType == CLEARANCE_CONSTRAINT ) { - if( override < m_designSettings->m_MinClearance ) + if( override_val < m_designSettings->m_MinClearance ) { - override = m_designSettings->m_MinClearance; - m_msg = _( "board minimum" ); + override_val = m_designSettings->m_MinClearance; + msg = _( "board minimum" ); REPORT( "" ) REPORT( wxString::Format( _( "Board minimum clearance: %s." ), - REPORT_VALUE( override ) ) ) + REPORT_VALUE( override_val ) ) ) } } else { - if( override < m_designSettings->m_HoleClearance ) + if( override_val < m_designSettings->m_HoleClearance ) { - override = m_designSettings->m_HoleClearance; - m_msg = _( "board minimum hole" ); + override_val = m_designSettings->m_HoleClearance; + msg = _( "board minimum hole" ); REPORT( "" ) REPORT( wxString::Format( _( "Board minimum hole clearance: %s." ), - REPORT_VALUE( override ) ) ) + REPORT_VALUE( override_val ) ) ) } } - constraint.SetName( m_msg ); - constraint.m_Value.SetMin( override ); + constraint.SetName( msg ); + constraint.m_Value.SetMin( override_val ); return constraint; } } @@ -1108,6 +1109,7 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO int localA = ac ? ac->GetLocalClearance( nullptr ) : 0; int localB = bc ? bc->GetLocalClearance( nullptr ) : 0; int clearance = global; + wxString msg; if( localA > 0 ) { @@ -1117,7 +1119,7 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO REPORT_VALUE( localA ) ) ) if( localA > clearance ) - clearance = ac->GetLocalClearance( &m_msg ); + clearance = ac->GetLocalClearance( &msg ); } if( localB > 0 ) @@ -1128,13 +1130,13 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO REPORT_VALUE( localB ) ) ) if( localB > clearance ) - clearance = bc->GetLocalClearance( &m_msg ); + clearance = bc->GetLocalClearance( &msg ); } if( localA > global || localB > global ) { constraint.SetParentRule( nullptr ); - constraint.SetName( m_msg ); + constraint.SetName( msg ); constraint.m_Value.SetMin( clearance ); return constraint; } diff --git a/pcbnew/drc/drc_engine.h b/pcbnew/drc/drc_engine.h index 049a2d9f58..64486c3747 100644 --- a/pcbnew/drc/drc_engine.h +++ b/pcbnew/drc/drc_engine.h @@ -233,7 +233,6 @@ protected: REPORTER* m_reporter; PROGRESS_REPORTER* m_progressReporter; - wxString m_msg; // Allocating strings gets expensive enough to want to avoid it std::shared_ptr m_debugOverlay; }; diff --git a/pcbnew/drc/drc_test_provider.h b/pcbnew/drc/drc_test_provider.h index c410aeb41a..bc19a3d282 100644 --- a/pcbnew/drc/drc_test_provider.h +++ b/pcbnew/drc/drc_test_provider.h @@ -132,8 +132,6 @@ protected: std::unordered_map m_stats; bool m_isRuleDriven = true; bool m_enabled = true; - - wxString m_msg; // Allocating strings gets expensive enough to want to avoid it }; #endif // DRC_TEST_PROVIDER__H diff --git a/pcbnew/drc/drc_test_provider_annular_width.cpp b/pcbnew/drc/drc_test_provider_annular_width.cpp index e4abbd6d24..25a7b051eb 100644 --- a/pcbnew/drc/drc_test_provider_annular_width.cpp +++ b/pcbnew/drc/drc_test_provider_annular_width.cpp @@ -123,20 +123,21 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() if( fail_min || fail_max ) { std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_ANNULAR_WIDTH ); + wxString msg; if( fail_min ) - m_msg.Printf( _( "(%s min annular width %s; actual %s)" ), + msg.Printf( _( "(%s min annular width %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), v_min ), MessageTextFromValue( userUnits(), annularWidth ) ); if( fail_max ) - m_msg.Printf( _( "(%s max annular width %s; actual %s)" ), + msg.Printf( _( "(%s max annular width %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), v_max ), MessageTextFromValue( userUnits(), annularWidth ) ); - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); drcItem->SetItems( item ); drcItem->SetViolatingRule( constraint.GetParentRule() ); diff --git a/pcbnew/drc/drc_test_provider_copper_clearance.cpp b/pcbnew/drc/drc_test_provider_copper_clearance.cpp index d04647f8e0..9db0b24a8b 100644 --- a/pcbnew/drc/drc_test_provider_copper_clearance.cpp +++ b/pcbnew/drc/drc_test_provider_copper_clearance.cpp @@ -307,13 +307,14 @@ bool DRC_TEST_PROVIDER_COPPER_CLEARANCE::testTrackAgainstItem( PCB_TRACK* track, if( trackShape->Collide( otherShape.get(), clearance - m_drcEpsilon, &actual, &pos ) ) { std::shared_ptr drce = DRC_ITEM::Create( DRCE_CLEARANCE ); + wxString msg; - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), clearance ), MessageTextFromValue( userUnits(), actual ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); drce->SetItems( track, other ); drce->SetViolatingRule( constraint.GetParentRule() ); @@ -353,14 +354,16 @@ bool DRC_TEST_PROVIDER_COPPER_CLEARANCE::testTrackAgainstItem( PCB_TRACK* track, std::max( 0, clearance - m_drcEpsilon ), &actual, &pos ) ) { - std::shared_ptr drce = DRC_ITEM::Create( DRCE_HOLE_CLEARANCE ); - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + std::shared_ptr drce = DRC_ITEM::Create( DRCE_HOLE_CLEARANCE ); + wxString msg; + + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), clearance ), MessageTextFromValue( userUnits(), actual ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); drce->SetItems( track, other ); drce->SetViolatingRule( constraint.GetParentRule() ); @@ -441,13 +444,14 @@ void DRC_TEST_PROVIDER_COPPER_CLEARANCE::testItemAgainstZones( BOARD_ITEM* aItem &actual, &pos ) ) { std::shared_ptr drce = DRC_ITEM::Create( DRCE_CLEARANCE ); + wxString msg; - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), clearance ), MessageTextFromValue( userUnits(), actual ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); drce->SetItems( aItem, zone ); drce->SetViolatingRule( constraint.GetParentRule() ); @@ -486,13 +490,14 @@ void DRC_TEST_PROVIDER_COPPER_CLEARANCE::testItemAgainstZones( BOARD_ITEM* aItem &actual, &pos ) ) { std::shared_ptr drce = DRC_ITEM::Create( DRCE_HOLE_CLEARANCE ); + wxString msg; - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), clearance ), MessageTextFromValue( userUnits(), actual ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); drce->SetItems( aItem, zone ); drce->SetViolatingRule( constraint.GetParentRule() ); @@ -650,12 +655,13 @@ bool DRC_TEST_PROVIDER_COPPER_CLEARANCE::testPadAgainstItem( PAD* pad, SHAPE* pa && testShorting ) { std::shared_ptr drce = DRC_ITEM::Create( DRCE_SHORTING_ITEMS ); + wxString msg; - m_msg.Printf( _( "(nets %s and %s)" ), + msg.Printf( _( "(nets %s and %s)" ), pad->GetNetname(), otherPad->GetNetname() ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); drce->SetItems( pad, otherPad ); reportViolation( drce, otherPad->GetPosition() ); @@ -674,13 +680,14 @@ bool DRC_TEST_PROVIDER_COPPER_CLEARANCE::testPadAgainstItem( PAD* pad, SHAPE* pa &actual, &pos ) ) { std::shared_ptr drce = DRC_ITEM::Create( DRCE_CLEARANCE ); + wxString msg; - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), clearance ), MessageTextFromValue( userUnits(), actual ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); drce->SetItems( pad, other ); drce->SetViolatingRule( constraint.GetParentRule() ); @@ -702,13 +709,14 @@ bool DRC_TEST_PROVIDER_COPPER_CLEARANCE::testPadAgainstItem( PAD* pad, SHAPE* pa &actual, &pos ) ) { std::shared_ptr drce = DRC_ITEM::Create( DRCE_HOLE_CLEARANCE ); + wxString msg; - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), clearance ), MessageTextFromValue( userUnits(), actual ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); drce->SetItems( pad, other ); drce->SetViolatingRule( constraint.GetParentRule() ); @@ -724,13 +732,14 @@ bool DRC_TEST_PROVIDER_COPPER_CLEARANCE::testPadAgainstItem( PAD* pad, SHAPE* pa &actual, &pos ) ) { std::shared_ptr drce = DRC_ITEM::Create( DRCE_HOLE_CLEARANCE ); + wxString msg; - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), clearance ), MessageTextFromValue( userUnits(), actual ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); drce->SetItems( pad, other ); drce->SetViolatingRule( constraint.GetParentRule() ); @@ -749,13 +758,14 @@ bool DRC_TEST_PROVIDER_COPPER_CLEARANCE::testPadAgainstItem( PAD* pad, SHAPE* pa &actual, &pos ) ) { std::shared_ptr drce = DRC_ITEM::Create( DRCE_HOLE_CLEARANCE ); + wxString msg; - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), clearance ), MessageTextFromValue( userUnits(), actual ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); drce->SetItems( pad, otherVia ); drce->SetViolatingRule( constraint.GetParentRule() ); @@ -986,13 +996,14 @@ void DRC_TEST_PROVIDER_COPPER_CLEARANCE::testZonesToZones() else { drce = DRC_ITEM::Create( DRCE_CLEARANCE ); + wxString msg; - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), zone2zoneClearance ), MessageTextFromValue( userUnits(), conflict.second ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); } drce->SetItems( zoneRef, zoneToTest ); diff --git a/pcbnew/drc/drc_test_provider_courtyard_clearance.cpp b/pcbnew/drc/drc_test_provider_courtyard_clearance.cpp index 9914906925..9acf14424b 100644 --- a/pcbnew/drc/drc_test_provider_courtyard_clearance.cpp +++ b/pcbnew/drc/drc_test_provider_courtyard_clearance.cpp @@ -210,12 +210,13 @@ bool DRC_TEST_PROVIDER_COURTYARD_CLEARANCE::testCourtyardClearances() if( clearance > 0 ) { - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + wxString msg; + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), clearance ), MessageTextFromValue( userUnits(), actual ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); drce->SetViolatingRule( constraint.GetParentRule() ); } @@ -237,12 +238,13 @@ bool DRC_TEST_PROVIDER_COURTYARD_CLEARANCE::testCourtyardClearances() if( clearance > 0 ) { - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + wxString msg; + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), clearance ), MessageTextFromValue( userUnits(), actual ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); drce->SetViolatingRule( constraint.GetParentRule() ); } diff --git a/pcbnew/drc/drc_test_provider_diff_pair_coupling.cpp b/pcbnew/drc/drc_test_provider_diff_pair_coupling.cpp index a3f8a6eb26..5d2937e5af 100644 --- a/pcbnew/drc/drc_test_provider_diff_pair_coupling.cpp +++ b/pcbnew/drc/drc_test_provider_diff_pair_coupling.cpp @@ -459,13 +459,14 @@ bool test::DRC_TEST_PROVIDER_DIFF_PAIR_COUPLING::Run() if ( val.HasMax() && totalUncoupled > val.Max() ) { auto drce = DRC_ITEM::Create( DRCE_DIFF_PAIR_UNCOUPLED_LENGTH_TOO_LONG ); + wxString msg; - m_msg = wxString::Format( _( "(%s maximum uncoupled length: %s; actual: %s)" ), + msg = wxString::Format( _( "(%s maximum uncoupled length: %s; actual: %s)" ), maxUncoupledConstraint->GetParentRule()->m_Name, MessageTextFromValue( userUnits(), val.Max() ), MessageTextFromValue( userUnits(), totalUncoupled ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); auto pit = it.second.itemsP.begin(); auto nit = it.second.itemsN.begin(); @@ -495,22 +496,23 @@ bool test::DRC_TEST_PROVIDER_DIFF_PAIR_COUPLING::Run() { auto val = gapConstraint->GetValue(); auto drcItem = DRC_ITEM::Create( DRCE_DIFF_PAIR_GAP_OUT_OF_RANGE ); + wxString msg; - m_msg = drcItem->GetErrorText() + wxT( " (" ) + + msg = drcItem->GetErrorText() + wxT( " (" ) + gapConstraint->GetParentRule()->m_Name + wxS( " " ); if( val.HasMin() ) - m_msg += wxString::Format( _( "minimum gap: %s; " ), + msg += wxString::Format( _( "minimum gap: %s; " ), MessageTextFromValue( userUnits(), val.Min() ) ); if( val.HasMax() ) - m_msg += wxString::Format( _( "maximum gap: %s; " ), + msg += wxString::Format( _( "maximum gap: %s; " ), MessageTextFromValue( userUnits(), val.Max() ) ); - m_msg += wxString::Format( _( "actual: %s)" ), + msg += wxString::Format( _( "actual: %s)" ), MessageTextFromValue( userUnits(), cpair.computedGap ) ); - drcItem->SetErrorMessage( m_msg ); + drcItem->SetErrorMessage( msg ); drcItem->AddItem( cpair.parentP ); drcItem->AddItem( cpair.parentN ); diff --git a/pcbnew/drc/drc_test_provider_disallow.cpp b/pcbnew/drc/drc_test_provider_disallow.cpp index c12acfe620..aa872f0bd3 100644 --- a/pcbnew/drc/drc_test_provider_disallow.cpp +++ b/pcbnew/drc/drc_test_provider_disallow.cpp @@ -85,11 +85,12 @@ bool DRC_TEST_PROVIDER_DISALLOW::Run() if( constraint.m_DisallowFlags ) { std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_ALLOWED_ITEMS ); + wxString msg; - m_msg.Printf( drcItem->GetErrorText() + wxS( " (%s)" ), + msg.Printf( drcItem->GetErrorText() + wxS( " (%s)" ), constraint.GetName() ); - drcItem->SetErrorMessage( m_msg ); + drcItem->SetErrorMessage( msg ); drcItem->SetItems( item ); drcItem->SetViolatingRule( constraint.GetParentRule() ); diff --git a/pcbnew/drc/drc_test_provider_edge_clearance.cpp b/pcbnew/drc/drc_test_provider_edge_clearance.cpp index 372af7938e..7680ff42a0 100644 --- a/pcbnew/drc/drc_test_provider_edge_clearance.cpp +++ b/pcbnew/drc/drc_test_provider_edge_clearance.cpp @@ -97,12 +97,13 @@ bool DRC_TEST_PROVIDER_EDGE_CLEARANCE::testAgainstEdge( BOARD_ITEM* item, SHAPE* // Only report clearance info if there is any; otherwise it's just a straight collision if( minClearance > 0 ) { - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + wxString msg; + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), minClearance ), MessageTextFromValue( userUnits(), actual ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); } drce->SetItems( edge->m_Uuid, item->m_Uuid ); diff --git a/pcbnew/drc/drc_test_provider_hole_size.cpp b/pcbnew/drc/drc_test_provider_hole_size.cpp index d333d77ba6..83e9b5e501 100644 --- a/pcbnew/drc/drc_test_provider_hole_size.cpp +++ b/pcbnew/drc/drc_test_provider_hole_size.cpp @@ -156,23 +156,24 @@ void DRC_TEST_PROVIDER_HOLE_SIZE::checkPad( PAD* aPad ) if( fail_min || fail_max ) { std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_DRILL_OUT_OF_RANGE ); + wxString msg; if( fail_min ) { - m_msg.Printf( _( "(%s min width %s; actual %s)" ), + msg.Printf( _( "(%s min width %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), constraintValue ), MessageTextFromValue( userUnits(), holeMinor ) ); } else { - m_msg.Printf( _( "(%s max width %s; actual %s)" ), + msg.Printf( _( "(%s max width %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), constraintValue ), MessageTextFromValue( userUnits(), holeMajor ) ); } - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); drcItem->SetItems( aPad ); drcItem->SetViolatingRule( constraint.GetParentRule() ); @@ -221,23 +222,24 @@ void DRC_TEST_PROVIDER_HOLE_SIZE::checkVia( PCB_VIA* via, bool aExceedMicro, boo if( fail_min || fail_max ) { std::shared_ptr drcItem = DRC_ITEM::Create( errorCode ); + wxString msg; if( fail_min ) { - m_msg.Printf( _( "(%s min width %s; actual %s)" ), + msg.Printf( _( "(%s min width %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), constraintValue ), MessageTextFromValue( userUnits(), via->GetDrillValue() ) ); } else { - m_msg.Printf( _( "(%s max width %s; actual %s)" ), + msg.Printf( _( "(%s max width %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), constraintValue ), MessageTextFromValue( userUnits(), via->GetDrillValue() ) ); } - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); drcItem->SetItems( via ); drcItem->SetViolatingRule( constraint.GetParentRule() ); diff --git a/pcbnew/drc/drc_test_provider_hole_to_hole.cpp b/pcbnew/drc/drc_test_provider_hole_to_hole.cpp index a579052449..ef98427204 100644 --- a/pcbnew/drc/drc_test_provider_hole_to_hole.cpp +++ b/pcbnew/drc/drc_test_provider_hole_to_hole.cpp @@ -309,13 +309,14 @@ bool DRC_TEST_PROVIDER_HOLE_TO_HOLE::testHoleAgainstHole( BOARD_ITEM* aItem, SHA if( minClearance >= 0 && actual < minClearance ) { std::shared_ptr drce = DRC_ITEM::Create( DRCE_DRILLED_HOLES_TOO_CLOSE ); + wxString msg; - m_msg.Printf( _( "(%s min %s; actual %s)" ), + msg.Printf( _( "(%s min %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), minClearance ), MessageTextFromValue( userUnits(), actual ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); drce->SetItems( aItem, aOther ); drce->SetViolatingRule( constraint.GetParentRule() ); diff --git a/pcbnew/drc/drc_test_provider_lvs.cpp b/pcbnew/drc/drc_test_provider_lvs.cpp index b1beb6480d..f387bfe106 100644 --- a/pcbnew/drc/drc_test_provider_lvs.cpp +++ b/pcbnew/drc/drc_test_provider_lvs.cpp @@ -116,13 +116,14 @@ void DRC_TEST_PROVIDER_LVS::testFootprints( NETLIST& aNetlist ) if( m_drcEngine->IsErrorLimitExceeded( DRCE_MISSING_FOOTPRINT ) ) break; - m_msg.Printf( _( "Missing footprint %s (%s)" ), + wxString msg; + msg.Printf( _( "Missing footprint %s (%s)" ), component->GetReference(), component->GetValue() ); std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_MISSING_FOOTPRINT ); - drcItem->SetErrorMessage( m_msg ); + drcItem->SetErrorMessage( msg ); reportViolation( drcItem, wxPoint() ); } else @@ -140,31 +141,34 @@ void DRC_TEST_PROVIDER_LVS::testFootprints( NETLIST& aNetlist ) if( !pcb_netname.IsEmpty() && sch_net.GetPinName().IsEmpty() ) { - m_msg.Printf( _( "No corresponding pin found in schematic." ) ); + wxString msg; + msg.Printf( _( "No corresponding pin found in schematic." ) ); std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT ); - drcItem->SetErrorMessage( m_msg ); + drcItem->SetErrorMessage( msg ); drcItem->SetItems( pad ); reportViolation( drcItem, footprint->GetPosition() ); } else if( pcb_netname.IsEmpty() && !sch_net.GetNetName().IsEmpty() ) { - m_msg.Printf( _( "Pad missing net given by schematic (%s)." ), + wxString msg; + msg.Printf( _( "Pad missing net given by schematic (%s)." ), sch_net.GetNetName() ); std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT ); - drcItem->SetErrorMessage( m_msg ); + drcItem->SetErrorMessage( msg ); drcItem->SetItems( pad ); reportViolation( drcItem, footprint->GetPosition() ); } else if( pcb_netname != sch_net.GetNetName() ) { - m_msg.Printf( _( "Pad net (%s) doesn't match net given by schematic (%s)." ), + wxString msg; + msg.Printf( _( "Pad net (%s) doesn't match net given by schematic (%s)." ), pcb_netname, sch_net.GetNetName() ); std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT ); - drcItem->SetErrorMessage( m_msg ); + drcItem->SetErrorMessage( msg ); drcItem->SetItems( pad ); reportViolation( drcItem, footprint->GetPosition() ); } @@ -179,11 +183,12 @@ void DRC_TEST_PROVIDER_LVS::testFootprints( NETLIST& aNetlist ) if( !footprint->FindPadByNumber( sch_net.GetPinName() ) ) { - m_msg.Printf( _( "No pad found for pin %s in schematic." ), + wxString msg; + msg.Printf( _( "No pad found for pin %s in schematic." ), sch_net.GetPinName() ); std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT ); - drcItem->SetErrorMessage( m_msg ); + drcItem->SetErrorMessage( msg ); drcItem->SetItems( footprint ); reportViolation( drcItem, footprint->GetPosition() ); } diff --git a/pcbnew/drc/drc_test_provider_matched_length.cpp b/pcbnew/drc/drc_test_provider_matched_length.cpp index bd814180ec..e50b27402d 100644 --- a/pcbnew/drc/drc_test_provider_matched_length.cpp +++ b/pcbnew/drc/drc_test_provider_matched_length.cpp @@ -114,23 +114,24 @@ void DRC_TEST_PROVIDER_MATCHED_LENGTH::checkLengths( DRC_CONSTRAINT& aConstraint if( ( minViolation || maxViolation ) ) { std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_LENGTH_OUT_OF_RANGE ); + wxString msg; if( minViolation ) { - m_msg.Printf( _( "(%s min length: %s; actual: %s)" ), + msg.Printf( _( "(%s min length: %s; actual: %s)" ), aConstraint.GetName(), MessageTextFromValue( userUnits(), minLen ), MessageTextFromValue( userUnits(), ent.total ) ); } else if( maxViolation ) { - m_msg.Printf( _( "(%s max length: %s; actual: %s)" ), + msg.Printf( _( "(%s max length: %s; actual: %s)" ), aConstraint.GetName(), MessageTextFromValue( userUnits(), maxLen ), MessageTextFromValue( userUnits(), ent.total ) ); } - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); for( auto offendingTrack : ent.items ) drcItem->AddItem( offendingTrack ); @@ -158,15 +159,16 @@ void DRC_TEST_PROVIDER_MATCHED_LENGTH::checkSkews( DRC_CONSTRAINT& aConstraint, if( aConstraint.GetValue().HasMax() && abs( skew ) > aConstraint.GetValue().Max() ) { std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_SKEW_OUT_OF_RANGE ); + wxString msg; - m_msg.Printf( _( "(%s max skew: %s; actual: %s; average net length: %s; actual: %s)" ), + msg.Printf( _( "(%s max skew: %s; actual: %s; average net length: %s; actual: %s)" ), aConstraint.GetName(), MessageTextFromValue( userUnits(), aConstraint.GetValue().Max() ), MessageTextFromValue( userUnits(), skew ), MessageTextFromValue( userUnits(), avgLength ), MessageTextFromValue( userUnits(), ent.total ) ); - drcItem->SetErrorMessage( drcItem->GetErrorText() + " " + m_msg ); + drcItem->SetErrorMessage( drcItem->GetErrorText() + " " + msg ); for( BOARD_CONNECTED_ITEM* offendingTrack : ent.items ) drcItem->SetItems( offendingTrack ); @@ -187,13 +189,14 @@ void DRC_TEST_PROVIDER_MATCHED_LENGTH::checkViaCounts( DRC_CONSTRAINT& aConstrai if( aConstraint.GetValue().HasMax() && ent.viaCount > aConstraint.GetValue().Max() ) { std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_TOO_MANY_VIAS ); + wxString msg; - m_msg.Printf( _( "(%s max count: %d; actual: %d)" ), + msg.Printf( _( "(%s max count: %d; actual: %d)" ), aConstraint.GetName(), aConstraint.GetValue().Max(), ent.viaCount ); - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); for( auto offendingTrack : ent.items ) drcItem->SetItems( offendingTrack ); diff --git a/pcbnew/drc/drc_test_provider_misc.cpp b/pcbnew/drc/drc_test_provider_misc.cpp index d436729e27..d748a6bfd8 100644 --- a/pcbnew/drc/drc_test_provider_misc.cpp +++ b/pcbnew/drc/drc_test_provider_misc.cpp @@ -112,10 +112,11 @@ void DRC_TEST_PROVIDER_MISC::testOutline() else { std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_INVALID_OUTLINE ); + wxString msg; - m_msg.Printf( _( "(no edges found on Edge.Cuts layer)" ) ); + msg.Printf( _( "(no edges found on Edge.Cuts layer)" ) ); - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); drcItem->SetItems( m_board ); reportViolation( drcItem, m_board->GetBoundingBox().Centre() ); @@ -199,10 +200,11 @@ void DRC_TEST_PROVIDER_MISC::testDisabledLayers() if( badLayer != UNDEFINED_LAYER ) { std::shared_ptrdrcItem = DRC_ITEM::Create( DRCE_DISABLED_LAYER_ITEM ); + wxString msg; - m_msg.Printf( _( "(layer %s)" ), LayerName( badLayer ) ); + msg.Printf( _( "(layer %s)" ), LayerName( badLayer ) ); - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); drcItem->SetItems( item ); reportViolation( drcItem, item->GetPosition() ); diff --git a/pcbnew/drc/drc_test_provider_physical_clearance.cpp b/pcbnew/drc/drc_test_provider_physical_clearance.cpp new file mode 100644 index 0000000000..ac7154ebeb --- /dev/null +++ b/pcbnew/drc/drc_test_provider_physical_clearance.cpp @@ -0,0 +1,860 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2021-2022 KiCad Developers. + * + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + Physical clearance tests. + + Errors generated: + - DRCE_PHYSICAL_CLEARANCE + - DRCE_PHYSICAL_HOLE_CLEARANCE +*/ + +class DRC_TEST_PROVIDER_PHYSICAL_CLEARANCE : public DRC_TEST_PROVIDER_CLEARANCE_BASE +{ +public: + DRC_TEST_PROVIDER_PHYSICAL_CLEARANCE () : + DRC_TEST_PROVIDER_CLEARANCE_BASE() + { + } + + virtual ~DRC_TEST_PROVIDER_PHYSICAL_CLEARANCE() + { + } + + virtual bool Run() override; + + virtual const wxString GetName() const override + { + return wxT( "physical_clearance" ); + }; + + virtual const wxString GetDescription() const override + { + return wxT( "Tests item clearances irrespective of nets" ); + } + +private: + bool testItemAgainstItem( BOARD_ITEM* item, SHAPE* itemShape, PCB_LAYER_ID layer, + BOARD_ITEM* other ); + + void testItemAgainstZones( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer ); + + void testShapeLineChain( const SHAPE_LINE_CHAIN& aOutline, int aLineWidth, PCB_LAYER_ID aLayer, + BOARD_ITEM* aParentItem, DRC_CONSTRAINT& aConstraint ); + + void testZoneLayer( ZONE* aZone, PCB_LAYER_ID aLayer, DRC_CONSTRAINT& aConstraint ); + +private: + DRC_RTREE m_itemTree; + std::vector m_zones; +}; + + +bool DRC_TEST_PROVIDER_PHYSICAL_CLEARANCE::Run() +{ + m_board = m_drcEngine->GetBoard(); + m_itemTree.clear(); + m_zones.clear(); + m_zones.reserve( m_board->Zones().size() ); + + int errorMax = m_board->GetDesignSettings().m_MaxError; + DRC_CONSTRAINT worstConstraint; + + if( m_drcEngine->QueryWorstConstraint( PHYSICAL_CLEARANCE_CONSTRAINT, worstConstraint ) ) + m_largestClearance = worstConstraint.GetValue().Min(); + + if( m_drcEngine->QueryWorstConstraint( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT, worstConstraint ) ) + m_largestClearance = std::max( m_largestClearance, worstConstraint.GetValue().Min() ); + + if( m_largestClearance <= 0 ) + { + reportAux( wxT( "No Clearance constraints found. Tests not run." ) ); + return true; // continue with other tests + } + + for( ZONE* zone : m_board->Zones() ) + { + if( !zone->GetIsRuleArea() ) + { + m_zones.push_back( zone ); + m_largestClearance = std::max( m_largestClearance, zone->GetLocalClearance() ); + } + } + + for( FOOTPRINT* footprint : m_board->Footprints() ) + { + for( PAD* pad : footprint->Pads() ) + m_largestClearance = std::max( m_largestClearance, pad->GetLocalClearance() ); + + for( ZONE* zone : footprint->Zones() ) + { + if( !zone->GetIsRuleArea() ) + { + m_zones.push_back( zone ); + m_largestClearance = std::max( m_largestClearance, zone->GetLocalClearance() ); + } + } + } + + reportAux( wxT( "Worst clearance : %d nm" ), m_largestClearance ); + + // This is the number of tests between 2 calls to the progress bar + size_t delta = 100; + size_t count = 0; + size_t ii = 0; + + if( !reportPhase( _( "Gathering items..." ) ) ) + return false; // DRC cancelled + + static const std::vector itemTypes = { + PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, + PCB_FOOTPRINT_T, + PCB_PAD_T, + PCB_SHAPE_T, PCB_FP_SHAPE_T, + PCB_TEXT_T, PCB_FP_TEXT_T, PCB_TEXTBOX_T, PCB_FP_TEXTBOX_T, + PCB_DIMENSION_T + }; + + static const LSET courtyards( 2, F_CrtYd, B_CrtYd ); + + forEachGeometryItem( itemTypes, LSET::AllLayersMask(), + [&]( BOARD_ITEM* item ) -> bool + { + ++count; + return true; + } ); + + forEachGeometryItem( itemTypes, LSET::AllLayersMask(), + [&]( BOARD_ITEM* item ) -> bool + { + if( !reportProgress( ii++, count, delta ) ) + return false; + + LSET layers = item->GetLayerSet(); + + // Special-case holes and edge-cuts which pierce all physical layers + if( item->Type() == PCB_PAD_T ) + { + PAD* pad = static_cast( item ); + + if( pad->GetDrillSizeX() > 0 && pad->GetDrillSizeY() > 0 ) + layers |= LSET::PhysicalLayersMask() | courtyards; + } + else if( item->Type() == PCB_VIA_T ) + { + PCB_VIA* via = static_cast( item ); + + if( via->GetDrill() > 0 ) + layers |= LSET::PhysicalLayersMask() | courtyards; + } + else if( item->Type() == PCB_FOOTPRINT_T ) + { + layers = courtyards; + } + else if( item->IsOnLayer( Edge_Cuts ) ) + { + layers |= LSET::PhysicalLayersMask() | courtyards; + } + + for( PCB_LAYER_ID layer : layers.Seq() ) + m_itemTree.Insert( item, layer, m_largestClearance ); + + return true; + } ); + + std::map< std::pair, int> checkedPairs; + ii = 0; + + if( !m_drcEngine->IsErrorLimitExceeded( DRCE_CLEARANCE ) + || !m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_CLEARANCE ) ) + { + if( !reportPhase( _( "Checking physical clearances..." ) ) ) + return false; // DRC cancelled + + forEachGeometryItem( itemTypes, LSET::AllLayersMask(), + [&]( BOARD_ITEM* item ) -> bool + { + if( !reportProgress( ii++, count, delta ) ) + return false; + + LSET layers = item->GetLayerSet(); + + if( item->Type() == PCB_FOOTPRINT_T ) + layers = courtyards; + + for( PCB_LAYER_ID layer : layers.Seq() ) + { + std::shared_ptr itemShape = item->GetEffectiveShape( layer ); + + m_itemTree.QueryColliding( item, layer, layer, + // Filter: + [&]( BOARD_ITEM* other ) -> bool + { + BOARD_ITEM* a = item; + BOARD_ITEM* b = other; + + // store canonical order so we don't collide in both + // directions (a:b and b:a) + if( static_cast( a ) > static_cast( b ) ) + std::swap( a, b ); + + if( checkedPairs.count( { a, b } ) ) + { + return false; + } + else + { + checkedPairs[ { a, b } ] = 1; + return true; + } + }, + // Visitor: + [&]( BOARD_ITEM* other ) -> bool + { + return testItemAgainstItem( item, itemShape.get(), layer, + other ); + }, + m_largestClearance ); + + testItemAgainstZones( item, layer ); + } + + return true; + } ); + } + + count = 0; + ii = 0; + + forEachGeometryItem( { PCB_ZONE_T, PCB_FP_ZONE_T, PCB_SHAPE_T, PCB_FP_SHAPE_T }, + LSET::AllCuMask(), + [&]( BOARD_ITEM* item ) -> bool + { + ZONE* zone = dynamic_cast( item ); + + if( zone && zone->GetIsRuleArea() ) + return true; // Continue with other items + + count += ( item->GetLayerSet() & LSET::AllCuMask() ).count(); + + return true; + } ); + + forEachGeometryItem( { PCB_ZONE_T, PCB_FP_ZONE_T, PCB_SHAPE_T, PCB_FP_SHAPE_T }, + LSET::AllCuMask(), + [&]( BOARD_ITEM* item ) -> bool + { + PCB_SHAPE* shape = dynamic_cast( item ); + ZONE* zone = dynamic_cast( item ); + + if( zone && zone->GetIsRuleArea() ) + return true; // Continue with other items + + for( PCB_LAYER_ID layer : item->GetLayerSet().Seq() ) + { + if( IsCopperLayer( layer ) ) + { + if( !reportProgress( ii++, count, delta ) ) + return false; + + DRC_CONSTRAINT c = m_drcEngine->EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, + item, nullptr, layer ); + + if( shape ) + { + switch( shape->GetShape() ) + { + case SHAPE_T::POLY: + testShapeLineChain( shape->GetPolyShape().Outline( 0 ), + shape->GetWidth(), layer, item, c ); + break; + + case SHAPE_T::BEZIER: + { + SHAPE_LINE_CHAIN asPoly; + + shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() ); + + for( const VECTOR2I& pt : shape->GetBezierPoints() ) + asPoly.Append( pt ); + + testShapeLineChain( asPoly, shape->GetWidth(), layer, item, c ); + break; + } + + case SHAPE_T::ARC: + { + SHAPE_LINE_CHAIN asPoly; + + VECTOR2I center = shape->GetCenter(); + EDA_ANGLE angle = -shape->GetArcAngle(); + double r = shape->GetRadius(); + int steps = GetArcToSegmentCount( r, errorMax, angle ); + + asPoly.Append( shape->GetStart() ); + + for( int step = 1; step <= steps; ++step ) + { + EDA_ANGLE rotation = ( angle * step ) / steps; + VECTOR2I pt = shape->GetStart(); + + RotatePoint( pt, center, rotation ); + asPoly.Append( pt ); + } + + testShapeLineChain( asPoly, shape->GetWidth(), layer, item, c ); + break; + } + + case SHAPE_T::RECT: + { + SHAPE_LINE_CHAIN asPoly; + std::vector pts = shape->GetRectCorners(); + asPoly.Append( pts[0] ); + asPoly.Append( pts[1] ); + asPoly.Append( pts[2] ); + asPoly.Append( pts[3] ); + asPoly.SetClosed( true ); + + testShapeLineChain( asPoly, shape->GetWidth(), layer, item, c ); + break; + } + + default: + UNIMPLEMENTED_FOR( shape->SHAPE_T_asString() ); + } + } + + if( zone ) + testZoneLayer( static_cast( item ), layer, c ); + } + + if( m_drcEngine->IsCancelled() ) + return false; + } + + return !m_drcEngine->IsCancelled(); + } ); + + reportRuleStatistics(); + + return !m_drcEngine->IsCancelled(); +} + + +void DRC_TEST_PROVIDER_PHYSICAL_CLEARANCE::testShapeLineChain( const SHAPE_LINE_CHAIN& aOutline, + int aLineWidth, PCB_LAYER_ID aLayer, + BOARD_ITEM* aParentItem, + DRC_CONSTRAINT& aConstraint ) +{ + // We don't want to collide with neighboring segments forming a curve until the concavity + // approaches 180 degrees. + double angleTolerance = DEG2RAD( 180.0 - ADVANCED_CFG::GetCfg().m_SliverAngleTolerance ); + int epsilon = m_board->GetDesignSettings().GetDRCEpsilon(); + int count = aOutline.SegmentCount(); + int clearance = aConstraint.GetValue().Min(); + + // Trigonometry is not cheap; cache seg angles + std::vector angles; + angles.reserve( count ); + + auto angleDiff = + []( double a, double b ) -> double + { + if( a > b ) + std::swap( a, b ); + + double diff = b - a; + + if( diff > M_PI ) + return 2 * M_PI - diff; + else + return diff; + }; + + for( int ii = 0; ii < count; ++ii ) + { + const SEG& seg = aOutline.CSegment( ii ); + + // NB: don't store angles of really short segments (which could point anywhere) + + if( seg.SquaredLength() > SEG::Square( epsilon * 2 ) ) + { + angles.push_back( EDA_ANGLE( seg.B - seg.A ).AsRadians() ); + } + else if( ii > 0 ) + { + angles.push_back( angles.back() ); + } + else + { + for( int jj = 1; jj < count; ++jj ) + { + const SEG& following = aOutline.CSegment( jj ); + + if( following.SquaredLength() > SEG::Square( epsilon * 2 ) || jj == count - 1 ) + { + angles.push_back( EDA_ANGLE( following.B - following.A ).AsRadians() ); + break; + } + } + } + } + + // Find collisions before reporting so that we can condense them into fewer reports. + std::vector< std::pair > collisions; + + for( int ii = 0; ii < count; ++ii ) + { + const SEG seg = aOutline.CSegment( ii ); + double segAngle = angles[ ii ]; + + // Exclude segments on either side of us until we reach the angle tolerance + int firstCandidate = ii + 1; + int lastCandidate = count - 1; + + while( firstCandidate < count ) + { + if( angleDiff( segAngle, angles[ firstCandidate ] ) < angleTolerance ) + firstCandidate++; + else + break; + } + + if( aOutline.IsClosed() ) + { + if( ii > 0 ) + lastCandidate = ii - 1; + + while( lastCandidate != std::min( firstCandidate, count - 1 ) ) + { + if( angleDiff( segAngle, angles[ lastCandidate ] ) < angleTolerance ) + lastCandidate = ( lastCandidate == 0 ) ? count - 1 : lastCandidate - 1; + else + break; + } + } + + // Now run the collision between seg and each candidate seg in the candidate range. + if( lastCandidate < ii ) + lastCandidate = count - 1; + + for( int jj = firstCandidate; jj <= lastCandidate; ++jj ) + { + const SEG candidate = aOutline.CSegment( jj ); + int actual; + + if( seg.Collide( candidate, clearance + aLineWidth - epsilon, &actual ) ) + { + VECTOR2I firstPoint = seg.NearestPoint( candidate ); + VECTOR2I secondPoint = candidate.NearestPoint( seg ); + VECTOR2I pos = ( firstPoint + secondPoint ) / 2; + + if( !collisions.empty() && + ( pos - collisions.back().first ).EuclideanNorm() < clearance * 2 ) + { + if( actual < collisions.back().second ) + { + collisions.back().first = pos; + collisions.back().second = actual; + } + + continue; + } + + collisions.push_back( { pos, actual } ); + } + } + } + + for( std::pair collision : collisions ) + { + std::shared_ptr drce = DRC_ITEM::Create( DRCE_CLEARANCE ); + wxString msg; + + msg.Printf( _( "(%s clearance %s; actual %s)" ), + aConstraint.GetName(), + MessageTextFromValue( userUnits(), clearance ), + MessageTextFromValue( userUnits(), collision.second ) ); + + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); + drce->SetItems( aParentItem ); + drce->SetViolatingRule( aConstraint.GetParentRule() ); + + reportViolation( drce, collision.first, aLayer ); + } +} + + +void DRC_TEST_PROVIDER_PHYSICAL_CLEARANCE::testZoneLayer( ZONE* aZone, PCB_LAYER_ID aLayer, + DRC_CONSTRAINT& aConstraint ) +{ + int epsilon = m_board->GetDesignSettings().GetDRCEpsilon(); + int clearance = aConstraint.GetValue().Min(); + SHAPE_POLY_SET fill = aZone->GetFilledPolysList( aLayer )->CloneDropTriangulation(); + + if( aConstraint.GetSeverity() == RPT_SEVERITY_IGNORE || clearance - epsilon <= 0 ) + return; + + // Turn fractured fill into outlines and holes + fill.Simplify( SHAPE_POLY_SET::PM_FAST ); + + for( int outlineIdx = 0; outlineIdx < fill.OutlineCount(); ++outlineIdx ) + { + SHAPE_LINE_CHAIN* firstOutline = &fill.Outline( outlineIdx ); + + // Step one: outline to outline clearance violations + + for( int ii = outlineIdx + 1; ii < fill.OutlineCount(); ++ii ) + { + SHAPE_LINE_CHAIN* secondOutline = &fill.Outline( ii ); + + for( int jj = 0; jj < secondOutline->SegmentCount(); ++jj ) + { + SEG secondSeg = secondOutline->Segment( jj ); + int actual; + VECTOR2I pos; + + if( firstOutline->Collide( secondSeg, clearance - epsilon, &actual, &pos ) ) + { + std::shared_ptr drce = DRC_ITEM::Create( DRCE_CLEARANCE ); + wxString msg; + + msg.Printf( _( "(%s clearance %s; actual %s)" ), + aConstraint.GetName(), + MessageTextFromValue( userUnits(), clearance ), + MessageTextFromValue( userUnits(), actual ) ); + + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); + drce->SetItems( aZone ); + drce->SetViolatingRule( aConstraint.GetParentRule() ); + + reportViolation( drce, pos, aLayer ); + } + } + + if( m_drcEngine->IsCancelled() ) + return; + } + + // Step two: interior hole clearance violations + + for( int holeIdx = 0; holeIdx < fill.HoleCount( outlineIdx ); ++holeIdx ) + { + testShapeLineChain( fill.Hole( outlineIdx, holeIdx ), 0, aLayer, aZone, aConstraint ); + + if( m_drcEngine->IsCancelled() ) + return; + } + } +} + + +bool DRC_TEST_PROVIDER_PHYSICAL_CLEARANCE::testItemAgainstItem( BOARD_ITEM* item, + SHAPE* itemShape, + PCB_LAYER_ID layer, + BOARD_ITEM* other ) +{ + bool testClearance = !m_drcEngine->IsErrorLimitExceeded( DRCE_CLEARANCE ); + bool testHoles = !m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_CLEARANCE ); + DRC_CONSTRAINT constraint; + int clearance = 0; + int actual; + VECTOR2I pos; + + std::shared_ptr otherShape = other->GetEffectiveShape( layer ); + + if( testClearance ) + { + constraint = m_drcEngine->EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, item, other, layer ); + clearance = constraint.GetValue().Min(); + } + + if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance > 0 ) + { + if( itemShape->Collide( otherShape.get(), clearance, &actual, &pos ) ) + { + std::shared_ptr drce = DRC_ITEM::Create( DRCE_CLEARANCE ); + wxString msg; + + msg.Printf( _( "(%s clearance %s; actual %s)" ), + constraint.GetName(), + MessageTextFromValue( userUnits(), clearance ), + MessageTextFromValue( userUnits(), actual ) ); + + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); + drce->SetItems( item, other ); + drce->SetViolatingRule( constraint.GetParentRule() ); + + reportViolation( drce, pos, layer ); + } + } + + if( testHoles ) + { + std::unique_ptr itemHoleShape; + std::unique_ptr otherHoleShape; + clearance = 0; + + if( item->Type() == PCB_VIA_T ) + { + PCB_VIA* via = static_cast( item ); + pos = via->GetPosition(); + + if( via->GetLayerSet().Contains( layer ) ) + itemHoleShape.reset( new SHAPE_SEGMENT( pos, pos, via->GetDrill() ) ); + } + else if( item->Type() == PCB_PAD_T ) + { + PAD* pad = static_cast( item ); + + if( pad->GetDrillSize().x ) + itemHoleShape.reset( new SHAPE_SEGMENT( *pad->GetEffectiveHoleShape() ) ); + } + + if( other->Type() == PCB_VIA_T ) + { + PCB_VIA* via = static_cast( other ); + pos = via->GetPosition(); + + if( via->GetLayerSet().Contains( layer ) ) + otherHoleShape.reset( new SHAPE_SEGMENT( pos, pos, via->GetDrill() ) ); + } + else if( other->Type() == PCB_PAD_T ) + { + PAD* pad = static_cast( other ); + + if( pad->GetDrillSize().x ) + otherHoleShape.reset( new SHAPE_SEGMENT( *pad->GetEffectiveHoleShape() ) ); + } + + if( itemHoleShape || otherHoleShape ) + { + constraint = m_drcEngine->EvalRules( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT, other, item, + layer ); + clearance = constraint.GetValue().Min(); + } + + if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance > 0 ) + { + if( itemHoleShape && itemHoleShape->Collide( otherShape.get(), clearance, &actual, &pos ) ) + { + std::shared_ptr drce = DRC_ITEM::Create( DRCE_HOLE_CLEARANCE ); + wxString msg; + + msg.Printf( _( "(%s clearance %s; actual %s)" ), + constraint.GetName(), + MessageTextFromValue( userUnits(), clearance ), + MessageTextFromValue( userUnits(), actual ) ); + + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); + drce->SetItems( item, other ); + drce->SetViolatingRule( constraint.GetParentRule() ); + + reportViolation( drce, pos, layer ); + } + + if( otherHoleShape && otherHoleShape->Collide( itemShape, clearance, &actual, &pos ) ) + { + std::shared_ptr drce = DRC_ITEM::Create( DRCE_HOLE_CLEARANCE ); + wxString msg; + + msg.Printf( _( "(%s clearance %s; actual %s)" ), + constraint.GetName(), + MessageTextFromValue( userUnits(), clearance ), + MessageTextFromValue( userUnits(), actual ) ); + + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); + drce->SetItems( item, other ); + drce->SetViolatingRule( constraint.GetParentRule() ); + + reportViolation( drce, pos, layer ); + } + } + } + + return !m_drcEngine->IsCancelled(); +} + + +void DRC_TEST_PROVIDER_PHYSICAL_CLEARANCE::testItemAgainstZones( BOARD_ITEM* aItem, + PCB_LAYER_ID aLayer ) +{ + for( ZONE* zone : m_zones ) + { + if( !zone->GetLayerSet().test( aLayer ) ) + continue; + + if( aItem->GetBoundingBox().Intersects( zone->GetCachedBoundingBox() ) ) + { + bool testClearance = !m_drcEngine->IsErrorLimitExceeded( DRCE_CLEARANCE ); + bool testHoles = !m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_CLEARANCE ); + + if( !testClearance && !testHoles ) + return; + + DRC_RTREE* zoneTree = m_board->m_CopperZoneRTrees[ zone ].get(); + EDA_RECT itemBBox = aItem->GetBoundingBox(); + DRC_CONSTRAINT constraint; + bool colliding; + int clearance = -1; + int actual; + VECTOR2I pos; + + if( testClearance ) + { + constraint = m_drcEngine->EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, aItem, zone, + aLayer ); + clearance = constraint.GetValue().Min(); + } + + if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance > 0 ) + { + std::shared_ptr itemShape = aItem->GetEffectiveShape( aLayer ); + + if( aItem->Type() == PCB_PAD_T ) + { + PAD* pad = static_cast( aItem ); + + if( !pad->FlashLayer( aLayer ) ) + { + if( pad->GetDrillSize().x == 0 && pad->GetDrillSize().y == 0 ) + continue; + + const SHAPE_SEGMENT* hole = pad->GetEffectiveHoleShape(); + int size = hole->GetWidth(); + + // Note: drill size represents finish size, which means the actual hole + // size is the plating thickness larger. + if( pad->GetAttribute() == PAD_ATTRIB::PTH ) + size += m_board->GetDesignSettings().GetHolePlatingThickness(); + + itemShape = std::make_shared( hole->GetSeg(), size ); + } + } + + if( zoneTree ) + { + colliding = zoneTree->QueryColliding( itemBBox, itemShape.get(), aLayer, + clearance, &actual, &pos ); + } + else + { + colliding = zone->Outline()->Collide( itemShape.get(), clearance, &actual, + &pos ); + } + + if( colliding ) + { + std::shared_ptr drce = DRC_ITEM::Create( DRCE_CLEARANCE ); + wxString msg; + + msg.Printf( _( "(%s clearance %s; actual %s)" ), + constraint.GetName(), + MessageTextFromValue( userUnits(), clearance ), + MessageTextFromValue( userUnits(), actual ) ); + + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); + drce->SetItems( aItem, zone ); + drce->SetViolatingRule( constraint.GetParentRule() ); + + reportViolation( drce, pos, aLayer ); + } + } + + if( testHoles && ( aItem->Type() == PCB_VIA_T || aItem->Type() == PCB_PAD_T ) ) + { + std::unique_ptr holeShape; + + if( aItem->Type() == PCB_VIA_T ) + { + PCB_VIA* via = static_cast( aItem ); + pos = via->GetPosition(); + + if( via->GetLayerSet().Contains( aLayer ) ) + holeShape.reset( new SHAPE_SEGMENT( pos, pos, via->GetDrill() ) ); + } + else if( aItem->Type() == PCB_PAD_T ) + { + PAD* pad = static_cast( aItem ); + + if( pad->GetDrillSize().x ) + holeShape.reset( new SHAPE_SEGMENT( *pad->GetEffectiveHoleShape() ) ); + } + + if( holeShape ) + { + constraint = m_drcEngine->EvalRules( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT, aItem, + zone, aLayer ); + clearance = constraint.GetValue().Min(); + + if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE + && clearance > 0 + && zoneTree->QueryColliding( itemBBox, holeShape.get(), aLayer, + clearance, &actual, &pos ) ) + { + std::shared_ptr drce = DRC_ITEM::Create( DRCE_HOLE_CLEARANCE ); + wxString msg; + + msg.Printf( _( "(%s clearance %s; actual %s)" ), + constraint.GetName(), + MessageTextFromValue( userUnits(), clearance ), + MessageTextFromValue( userUnits(), actual ) ); + + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); + drce->SetItems( aItem, zone ); + drce->SetViolatingRule( constraint.GetParentRule() ); + + reportViolation( drce, pos, aLayer ); + } + } + } + } + + if( m_drcEngine->IsCancelled() ) + return; + } +} + + +namespace detail +{ + static DRC_REGISTER_TEST_PROVIDER dummy; +} diff --git a/pcbnew/drc/drc_test_provider_silk_clearance.cpp b/pcbnew/drc/drc_test_provider_silk_clearance.cpp index 169d32ce48..ebcff1cffc 100644 --- a/pcbnew/drc/drc_test_provider_silk_clearance.cpp +++ b/pcbnew/drc/drc_test_provider_silk_clearance.cpp @@ -228,12 +228,14 @@ bool DRC_TEST_PROVIDER_SILK_CLEARANCE::Run() if( minClearance > 0 ) { - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + wxString msg; + + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetParentRule()->m_Name, MessageTextFromValue( userUnits(), minClearance ), MessageTextFromValue( userUnits(), actual ) ); - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); } drcItem->SetItems( aRefItem->parent, aTestItem->parent ); diff --git a/pcbnew/drc/drc_test_provider_silk_to_mask.cpp b/pcbnew/drc/drc_test_provider_silk_to_mask.cpp index 7e05959753..f1171880a6 100644 --- a/pcbnew/drc/drc_test_provider_silk_to_mask.cpp +++ b/pcbnew/drc/drc_test_provider_silk_to_mask.cpp @@ -157,12 +157,14 @@ bool DRC_TEST_PROVIDER_SILK_TO_MASK::Run() if( minClearance > 0 ) { - m_msg.Printf( _( "(%s clearance %s; actual %s)" ), + wxString msg; + + msg.Printf( _( "(%s clearance %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), minClearance ), MessageTextFromValue( userUnits(), actual ) ); - drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); } drce->SetItems( aRefItem->parent, aTestItem->parent ); diff --git a/pcbnew/drc/drc_test_provider_solder_mask.cpp b/pcbnew/drc/drc_test_provider_solder_mask.cpp new file mode 100644 index 0000000000..e664be8dc9 --- /dev/null +++ b/pcbnew/drc/drc_test_provider_solder_mask.cpp @@ -0,0 +1,700 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2004-2022 KiCad Developers. + * + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + Solder mask tests. Checks for silkscreen which is clipped by mask openings and for bridges + between mask apertures with different nets. + Errors generated: + - DRCE_SILK_CLEARANCE + - DRCE_SOLDERMASK_BRIDGE +*/ + +class DRC_TEST_PROVIDER_SOLDER_MASK : public ::DRC_TEST_PROVIDER +{ +public: + DRC_TEST_PROVIDER_SOLDER_MASK (): + m_board( nullptr ), + m_webWidth( 0 ), + m_maxError( 0 ), + m_largestClearance( 0 ) + { + m_bridgeRule.m_Name = _( "board setup solder mask min width" ); + } + + virtual ~DRC_TEST_PROVIDER_SOLDER_MASK() + { + } + + virtual bool Run() override; + + virtual const wxString GetName() const override + { + return wxT( "solder_mask_issues" ); + }; + + virtual const wxString GetDescription() const override + { + return wxT( "Tests for silkscreen being clipped by solder mask and copper being exposed " + "by mask apertures of other nets" ); + } + +private: + void addItemToRTrees( BOARD_ITEM* item ); + void buildRTrees(); + + void testSilkToMaskClearance(); + void testMaskBridges(); + + void testItemAgainstItems( BOARD_ITEM* aItem, const EDA_RECT& aItemBBox, + PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer ); + void testMaskItemAgainstZones( BOARD_ITEM* item, const EDA_RECT& itemBBox, + PCB_LAYER_ID refLayer, PCB_LAYER_ID targetLayer ); + +private: + DRC_RULE m_bridgeRule; + + BOARD* m_board; + int m_webWidth; + int m_maxError; + int m_largestClearance; + + std::unique_ptr m_tesselatedTree; + std::unique_ptr m_itemTree; + std::vector m_copperZones; + + std::map< std::tuple, int> m_checkedPairs; + + // Shapes used to define solder mask apertures don't have nets, so we assign them the + // first net that bridges their aperture (after which any other nets will generate + // violations). + std::map< std::pair, int> m_maskApertureNetMap; +}; + + +void DRC_TEST_PROVIDER_SOLDER_MASK::addItemToRTrees( BOARD_ITEM* item ) +{ + ZONE* solderMask = m_board->m_SolderMask; + + if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T ) + { + ZONE* zone = static_cast( item ); + + for( PCB_LAYER_ID layer : { F_Mask, B_Mask } ) + { + if( zone->IsOnLayer( layer ) ) + { + solderMask->GetFill( layer )->BooleanAdd( *zone->GetFilledPolysList( layer ), + SHAPE_POLY_SET::PM_FAST ); + } + } + + if( zone->IsOnCopperLayer() && !zone->GetIsRuleArea() ) + m_copperZones.push_back( zone ); + } + else if( item->Type() == PCB_PAD_T ) + { + for( PCB_LAYER_ID layer : { F_Mask, B_Mask } ) + { + if( item->IsOnLayer( layer ) ) + { + PAD* pad = static_cast( item ); + int clearance = ( m_webWidth / 2 ) + pad->GetSolderMaskExpansion(); + + item->TransformShapeWithClearanceToPolygon( *solderMask->GetFill( layer ), layer, + clearance, m_maxError, ERROR_OUTSIDE ); + + m_itemTree->Insert( item, layer, m_largestClearance ); + } + } + } + else if( item->Type() == PCB_VIA_T ) + { + for( PCB_LAYER_ID layer : { F_Mask, B_Mask } ) + { + if( item->IsOnLayer( layer ) ) + { + PCB_VIA* via = static_cast( item ); + int clearance = ( m_webWidth / 2 ) + via->GetSolderMaskExpansion(); + + via->TransformShapeWithClearanceToPolygon( *solderMask->GetFill( layer ), layer, + clearance, m_maxError, ERROR_OUTSIDE ); + + m_itemTree->Insert( item, layer, m_largestClearance ); + } + } + } + else + { + for( PCB_LAYER_ID layer : { F_Mask, B_Mask } ) + { + if( item->IsOnLayer( layer ) ) + { + item->TransformShapeWithClearanceToPolygon( *solderMask->GetFill( layer ), + layer, m_webWidth / 2, m_maxError, + ERROR_OUTSIDE ); + + m_itemTree->Insert( item, layer, m_largestClearance ); + } + } + } +} + + +void DRC_TEST_PROVIDER_SOLDER_MASK::buildRTrees() +{ + ZONE* solderMask = m_board->m_SolderMask; + LSET layers = { 4, F_Mask, B_Mask, F_Cu, B_Cu }; + + size_t delta = 50; // Number of tests between 2 calls to the progress bar + int count = 0; + int ii = 0; + + solderMask->GetFill( F_Mask )->RemoveAllContours(); + solderMask->GetFill( B_Mask )->RemoveAllContours(); + + m_tesselatedTree = std::make_unique(); + m_itemTree = std::make_unique(); + m_copperZones.clear(); + + // Unlikely to be correct, but better than starting at 0 + m_copperZones.reserve( m_board->Zones().size() ); + + forEachGeometryItem( s_allBasicItems, layers, + [&]( BOARD_ITEM* item ) -> bool + { + ++count; + return true; + } ); + + forEachGeometryItem( s_allBasicItems, layers, + [&]( BOARD_ITEM* item ) -> bool + { + if( !reportProgress( ii++, count, delta ) ) + return false; + + addItemToRTrees( item ); + return true; + } ); + + solderMask->GetFill( F_Mask )->Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + solderMask->GetFill( B_Mask )->Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + + int numSegs = GetArcToSegmentCount( m_webWidth / 2, m_maxError, FULL_CIRCLE ); + + solderMask->GetFill( F_Mask )->Deflate( m_webWidth / 2, numSegs ); + solderMask->GetFill( B_Mask )->Deflate( m_webWidth / 2, numSegs ); + + solderMask->SetFillFlag( F_Mask, true ); + solderMask->SetFillFlag( B_Mask, true ); + solderMask->SetIsFilled( true ); + + solderMask->CacheTriangulation(); + + m_tesselatedTree->Insert( solderMask, F_Mask ); + m_tesselatedTree->Insert( solderMask, B_Mask ); + + m_checkedPairs.clear(); +} + + +void DRC_TEST_PROVIDER_SOLDER_MASK::testSilkToMaskClearance() +{ + LSET silkLayers = { 2, F_SilkS, B_SilkS }; + + size_t delta = 100; // Number of tests between 2 calls to the progress bar + int count = 0; + int ii = 0; + + forEachGeometryItem( s_allBasicItems, silkLayers, + [&]( BOARD_ITEM* item ) -> bool + { + ++count; + return true; + } ); + + forEachGeometryItem( s_allBasicItems, silkLayers, + [&]( BOARD_ITEM* item ) -> bool + { + if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_CLEARANCE ) ) + return false; + + if( !reportProgress( ii++, count, delta ) ) + return false; + + if( isInvisibleText( item ) ) + return true; + + for( PCB_LAYER_ID layer : silkLayers.Seq() ) + { + if( !item->IsOnLayer( layer ) ) + continue; + + EDA_RECT itemBBox = item->GetBoundingBox(); + DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( SILK_CLEARANCE_CONSTRAINT, + item, nullptr, layer ); + int clearance = constraint.GetValue().Min(); + int actual; + VECTOR2I pos; + + if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE || clearance <= 0 ) + return true; + + std::shared_ptr itemShape = item->GetEffectiveShape( layer ); + + if( m_tesselatedTree->QueryColliding( itemBBox, itemShape.get(), layer, + clearance, &actual, &pos ) ) + { + auto drce = DRC_ITEM::Create( DRCE_SILK_CLEARANCE ); + wxString msg; + + msg.Printf( _( "(%s clearance %s; actual %s)" ), + constraint.GetName(), + MessageTextFromValue( userUnits(), clearance ), + MessageTextFromValue( userUnits(), actual ) ); + + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); + drce->SetItems( item ); + drce->SetViolatingRule( constraint.GetParentRule() ); + + reportViolation( drce, pos, layer ); + } + } + + return true; + } ); +} + + +bool isMaskAperture( BOARD_ITEM* aItem ) +{ + static const LSET saved( 2, F_Mask, B_Mask ); + + LSET maskLayers = aItem->GetLayerSet() & saved; + LSET otherLayers = aItem->GetLayerSet() & ~saved; + + return maskLayers.count() > 0 && otherLayers.count() == 0; +} + + +bool isNullAperture( BOARD_ITEM* aItem ) +{ + if( aItem->Type() == PCB_PAD_T ) + { + PAD* pad = static_cast( aItem ); + + if( pad->GetAttribute() == PAD_ATTRIB::NPTH + && ( pad->GetShape() == PAD_SHAPE::CIRCLE || pad->GetShape() == PAD_SHAPE::OVAL ) + && pad->GetSize().x <= pad->GetDrillSize().x + && pad->GetSize().y <= pad->GetDrillSize().y ) + { + return true; + } + } + + return false; +} + + +void DRC_TEST_PROVIDER_SOLDER_MASK::testItemAgainstItems( BOARD_ITEM* aItem, + const EDA_RECT& aItemBBox, + PCB_LAYER_ID aRefLayer, + PCB_LAYER_ID aTargetLayer ) +{ + int itemNet = -1; + + if( aItem->IsConnected() ) + itemNet = static_cast( aItem )->GetNetCode(); + + PAD* pad = dynamic_cast( aItem ); + PCB_VIA* via = dynamic_cast( aItem ); + std::shared_ptr itemShape = aItem->GetEffectiveShape( aRefLayer ); + + m_itemTree->QueryColliding( aItem, aRefLayer, aTargetLayer, + // Filter: + [&]( BOARD_ITEM* other ) -> bool + { + PAD* otherPad = dynamic_cast( other ); + int otherNet = -1; + + if( other->IsConnected() ) + otherNet = static_cast( other )->GetNetCode(); + + if( otherNet > 0 && otherNet == itemNet ) + return false; + + if( isNullAperture( other ) ) + return false; + + if( aItem->GetParentFootprint() && other->GetParentFootprint() ) + { + int attr = static_cast( aItem->GetParentFootprint() )->GetAttributes(); + + if( attr & FP_ALLOW_SOLDERMASK_BRIDGES ) + return false; + } + + if( pad && otherPad && pad->GetParent() == otherPad->GetParent() ) + { + if( pad->SameLogicalPadAs( otherPad ) ) + return false; + } + + BOARD_ITEM* a = aItem; + BOARD_ITEM* b = other; + + // store canonical order so we don't collide in both directions + // (a:b and b:a) + if( static_cast( a ) > static_cast( b ) ) + std::swap( a, b ); + + if( m_checkedPairs.count( { a, b, aTargetLayer } ) ) + { + return false; + } + else + { + m_checkedPairs[ { a, b, aTargetLayer } ] = 1; + return true; + } + }, + // Visitor: + [&]( BOARD_ITEM* other ) -> bool + { + PAD* otherPad = dynamic_cast( other ); + PCB_VIA* otherVia = dynamic_cast( other ); + auto otherShape = other->GetEffectiveShape( aTargetLayer ); + int otherNet = -1; + + if( other->IsConnected() ) + otherNet = static_cast( other )->GetNetCode(); + + int actual; + VECTOR2I pos; + int clearance = 0; + + if( aRefLayer == F_Mask || aRefLayer == B_Mask ) + { + // Aperture-to-aperture must enforce web-min-width + clearance = m_webWidth; + } + else + { + // Copper-to-aperture uses the solder-mask-to-copper-clearance + clearance = m_board->GetDesignSettings().m_SolderMaskToCopperClearance; + } + + if( pad ) + clearance += pad->GetSolderMaskExpansion(); + else if( via ) + clearance += via->GetSolderMaskExpansion(); + + if( otherPad ) + clearance += otherPad->GetSolderMaskExpansion(); + else if( otherVia ) + clearance += otherVia->GetSolderMaskExpansion(); + + if( itemShape->Collide( otherShape.get(), clearance, &actual, &pos ) ) + { + // Simple mask apertures aren't associated with copper items, so they only + // constitute a bridge when they expose other copper items having at least + // two distinct nets. We use a map to record the first net exposed by each + // mask aperture. + + if( isMaskAperture( aItem ) ) + { + std::pair key = { aItem, aRefLayer }; + + if( m_maskApertureNetMap.count( key ) == 0 ) + { + m_maskApertureNetMap[ key ] = otherNet; + + // First net; no bridge yet.... + return true; + } + + if( m_maskApertureNetMap.at( key ) == otherNet && otherNet > 0 ) + return true; + } + + if( isMaskAperture( other ) ) + { + std::pair key = { other, aRefLayer }; + + if( m_maskApertureNetMap.count( key ) == 0 ) + { + m_maskApertureNetMap[ key ] = itemNet; + + // First net; no bridge yet.... + return true; + } + + if( m_maskApertureNetMap.at( key ) == itemNet && itemNet > 0 ) + return true; + } + + auto drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE ); + + if( aTargetLayer == F_Mask ) + { + drce->SetErrorMessage( _( "Front solder mask aperture bridges items with " + "different nets" ) ); + } + else + { + drce->SetErrorMessage( _( "Rear solder mask aperture bridges items with " + "different nets" ) ); + } + + drce->SetItems( aItem, other ); + drce->SetViolatingRule( &m_bridgeRule ); + reportViolation( drce, pos, aTargetLayer ); + } + + return !m_drcEngine->IsCancelled(); + }, + m_largestClearance ); +} + + +void DRC_TEST_PROVIDER_SOLDER_MASK::testMaskItemAgainstZones( BOARD_ITEM* aItem, + const EDA_RECT& aItemBBox, + PCB_LAYER_ID aMaskLayer, + PCB_LAYER_ID aTargetLayer ) +{ + for( ZONE* zone : m_copperZones ) + { + if( !zone->GetLayerSet().test( aTargetLayer ) ) + continue; + + int zoneNet = zone->GetNetCode(); + + if( aItem->IsConnected() ) + { + BOARD_CONNECTED_ITEM* connectedItem = static_cast( aItem ); + + if( zoneNet == connectedItem->GetNetCode() && zoneNet > 0 ) + continue; + } + + if( aItem->GetBoundingBox().Intersects( zone->GetCachedBoundingBox() ) ) + { + DRC_RTREE* zoneTree = m_board->m_CopperZoneRTrees[ zone ].get(); + int clearance = m_board->GetDesignSettings().m_SolderMaskToCopperClearance; + int actual; + VECTOR2I pos; + + std::shared_ptr itemShape = aItem->GetEffectiveShape( aMaskLayer ); + + if( aItem->Type() == PCB_PAD_T ) + { + PAD* pad = static_cast( aItem ); + + clearance += pad->GetSolderMaskExpansion(); + } + else if( aItem->Type() == PCB_VIA_T ) + { + PCB_VIA* via = static_cast( aItem ); + + clearance += via->GetSolderMaskExpansion(); + } + + if( zoneTree && zoneTree->QueryColliding( aItemBBox, itemShape.get(), aTargetLayer, + clearance, &actual, &pos ) ) + { + if( isMaskAperture( aItem ) ) + { + // Simple mask apertures aren't associated with copper items, so they only + // constitute a bridge when they expose other copper items having at least + // two distinct nets. We use a map to record the first net exposed by each + // mask aperture. + + std::pair key = { aItem, aMaskLayer }; + if( m_maskApertureNetMap.count( key ) == 0 ) + { + m_maskApertureNetMap[ key ] = zoneNet; + + // First net; no bridge yet.... + continue; + } + + if( m_maskApertureNetMap.at( key ) == zoneNet && zoneNet > 0 ) + continue; + } + + auto drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE ); + + if( aMaskLayer == F_Mask ) + { + drce->SetErrorMessage( _( "Front solder mask aperture bridges items with " + "different nets" ) ); + } + else + { + drce->SetErrorMessage( _( "Rear solder mask aperture bridges items with " + "different nets" ) ); + } + + drce->SetItems( aItem, zone ); + drce->SetViolatingRule( &m_bridgeRule ); + reportViolation( drce, pos, aTargetLayer ); + } + } + + if( m_drcEngine->IsCancelled() ) + return; + } +} + + +void DRC_TEST_PROVIDER_SOLDER_MASK::testMaskBridges() +{ + LSET copperAndMaskLayers = { 4, F_Mask, B_Mask, F_Cu, B_Cu }; + + size_t delta = 50; // Number of tests between 2 calls to the progress bar + int count = 0; + int ii = 0; + + forEachGeometryItem( s_allBasicItemsButZones, copperAndMaskLayers, + [&]( BOARD_ITEM* item ) -> bool + { + ++count; + return true; + } ); + + forEachGeometryItem( s_allBasicItemsButZones, copperAndMaskLayers, + [&]( BOARD_ITEM* item ) -> bool + { + if( m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) ) + return false; + + if( !reportProgress( ii++, count, delta ) ) + return false; + + EDA_RECT itemBBox = item->GetBoundingBox(); + + if( item->IsOnLayer( F_Mask ) && !isNullAperture( item ) ) + { + // Test for aperture-to-aperture collisions + testItemAgainstItems( item, itemBBox, F_Mask, F_Mask ); + + // Test for aperture-to-zone collisions + testMaskItemAgainstZones( item, itemBBox, F_Mask, F_Cu ); + } + else if( item->IsOnLayer( F_Cu ) ) + { + // Test for copper-item-to-aperture collisions + testItemAgainstItems( item, itemBBox, F_Cu, F_Mask ); + } + + if( item->IsOnLayer( B_Mask ) && !isNullAperture( item ) ) + { + // Test for aperture-to-aperture collisions + testItemAgainstItems( item, itemBBox, B_Mask, B_Mask ); + + // Test for aperture-to-zone collisions + testMaskItemAgainstZones( item, itemBBox, B_Mask, B_Cu ); + } + else if( item->IsOnLayer( B_Cu ) ) + { + // Test for copper-item-to-aperture collisions + testItemAgainstItems( item, itemBBox, B_Cu, B_Mask ); + } + + return true; + } ); +} + + +bool DRC_TEST_PROVIDER_SOLDER_MASK::Run() +{ + if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_CLEARANCE ) + && m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) ) + { + reportAux( wxT( "Solder mask violations ignored. Tests not run." ) ); + return true; // continue with other tests + } + + m_board = m_drcEngine->GetBoard(); + m_webWidth = m_board->GetDesignSettings().m_SolderMaskMinWidth; + m_maxError = m_board->GetDesignSettings().m_MaxError; + m_largestClearance = 0; + + for( FOOTPRINT* footprint : m_board->Footprints() ) + { + for( PAD* pad : footprint->Pads() ) + m_largestClearance = std::max( m_largestClearance, pad->GetSolderMaskExpansion() ); + } + + // Order is important here: m_webWidth must be added in before m_largestClearance is maxed + // with the various SILK_CLEARANCE_CONSTRAINTS. + m_largestClearance += m_largestClearance + m_webWidth; + + DRC_CONSTRAINT worstClearanceConstraint; + + if( m_drcEngine->QueryWorstConstraint( SILK_CLEARANCE_CONSTRAINT, worstClearanceConstraint ) ) + m_largestClearance = std::max( m_largestClearance, worstClearanceConstraint.m_Value.Min() ); + + reportAux( wxT( "Worst clearance : %d nm" ), m_largestClearance ); + + if( !reportPhase( _( "Building solder mask..." ) ) ) + return false; // DRC cancelled + + m_checkedPairs.clear(); + m_maskApertureNetMap.clear(); + + buildRTrees(); + + if( !reportPhase( _( "Checking solder mask to silk clearance..." ) ) ) + return false; // DRC cancelled + + testSilkToMaskClearance(); + + if( !reportPhase( _( "Checking solder mask web integrity..." ) ) ) + return false; // DRC cancelled + + testMaskBridges(); + + reportRuleStatistics(); + + return !m_drcEngine->IsCancelled(); +} + + +namespace detail +{ + static DRC_REGISTER_TEST_PROVIDER dummy; +} diff --git a/pcbnew/drc/drc_test_provider_text_dims.cpp b/pcbnew/drc/drc_test_provider_text_dims.cpp new file mode 100644 index 0000000000..a1b962b2de --- /dev/null +++ b/pcbnew/drc/drc_test_provider_text_dims.cpp @@ -0,0 +1,319 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2021-2022 KiCad Developers. + * + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + Text dimensions tests. + Errors generated: + - DRCE_TEXT_HEIGHT + - DRCE_TEXT_THICKNESS +*/ + +class DRC_TEST_PROVIDER_TEXT_DIMS : public DRC_TEST_PROVIDER +{ +public: + DRC_TEST_PROVIDER_TEXT_DIMS() + { + } + + virtual ~DRC_TEST_PROVIDER_TEXT_DIMS() + { + } + + virtual bool Run() override; + + virtual const wxString GetName() const override + { + return wxT( "text_dimensions" ); + }; + + virtual const wxString GetDescription() const override + { + return wxT( "Tests text height and thickness" ); + } +}; + + +bool DRC_TEST_PROVIDER_TEXT_DIMS::Run() +{ + const int delta = 100; // This is the number of tests between 2 calls to the progress bar + int count = 0; + int ii = 0; + + if( m_drcEngine->IsErrorLimitExceeded( DRCE_TEXT_HEIGHT ) + && m_drcEngine->IsErrorLimitExceeded( DRCE_TEXT_THICKNESS ) ) + { + reportAux( wxT( "Text dimension violations ignored. Tests not run." ) ); + return true; // continue with other tests + } + + if( !m_drcEngine->HasRulesForConstraintType( TEXT_HEIGHT_CONSTRAINT ) + && !m_drcEngine->HasRulesForConstraintType( TEXT_THICKNESS_CONSTRAINT ) ) + { + reportAux( wxT( "No text height or text thickness constraints found. Tests not run." ) ); + return true; // continue with other tests + } + + if( !reportPhase( _( "Checking text dimensions..." ) ) ) + return false; // DRC cancelled + + auto checkTextHeight = + [&]( BOARD_ITEM* item, EDA_TEXT* text ) -> bool + { + if( m_drcEngine->IsErrorLimitExceeded( DRCE_TEXT_HEIGHT ) ) + return false; + + DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( TEXT_HEIGHT_CONSTRAINT, item, + nullptr, item->GetLayer() ); + + if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE ) + return true; + + int actualHeight = text->GetTextSize().y; + + if( constraint.Value().HasMin() && actualHeight < constraint.Value().Min() ) + { + std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_TEXT_HEIGHT ); + wxString msg; + + msg.Printf( _( "(%s min height %s; actual %s)" ), + constraint.GetName(), + MessageTextFromValue( userUnits(), constraint.Value().Min() ), + MessageTextFromValue( userUnits(), actualHeight ) ); + + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); + drcItem->SetItems( item ); + drcItem->SetViolatingRule( constraint.GetParentRule() ); + + reportViolation( drcItem, item->GetPosition(), item->GetLayer() ); + } + + if( constraint.Value().HasMax() && actualHeight > constraint.Value().Max() ) + { + std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_TEXT_HEIGHT ); + wxString msg; + + msg.Printf( _( "(%s max height %s; actual %s)" ), + constraint.GetName(), + MessageTextFromValue( userUnits(), constraint.Value().Max() ), + MessageTextFromValue( userUnits(), actualHeight ) ); + + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); + drcItem->SetItems( item ); + drcItem->SetViolatingRule( constraint.GetParentRule() ); + + reportViolation( drcItem, item->GetPosition(), item->GetLayer() ); + } + + return true; + }; + + auto checkTextThickness = + [&]( BOARD_ITEM* item, EDA_TEXT* text ) -> bool + { + DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( TEXT_THICKNESS_CONSTRAINT, item, + nullptr, item->GetLayer() ); + + if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE ) + return true; + + KIFONT::FONT* font = text->GetDrawFont(); + + if( font->IsOutline() ) + { + if( !constraint.Value().HasMin() ) + return true; + + auto* glyphs = text->GetRenderCache( text->GetShownText() ); + bool collapsedStroke = false; + bool collapsedArea = false; + + for( const std::unique_ptr& glyph : *glyphs ) + { + auto outlineGlyph = static_cast( glyph.get() ); + int outlineCount = outlineGlyph->OutlineCount(); + int holeCount = 0; + + if( outlineCount == 0 ) + continue; // ignore spaces + + for( ii = 0; ii < outlineCount; ++ii ) + holeCount += outlineGlyph->HoleCount( ii ); + + SHAPE_POLY_SET poly = outlineGlyph->CloneDropTriangulation(); + poly.Deflate( constraint.Value().Min() / 2, 16 ); + poly.Simplify( SHAPE_POLY_SET::PM_FAST ); + + int resultingOutlineCount = poly.OutlineCount(); + int resultingHoleCount = 0; + + for( ii = 0; ii < resultingOutlineCount; ++ii ) + resultingHoleCount += poly.HoleCount( ii ); + + if( ( resultingOutlineCount != outlineCount ) + || ( resultingHoleCount != holeCount ) ) + { + collapsedStroke = true; + break; + } + + double glyphArea = outlineGlyph->Area(); + + if( glyphArea == 0 ) + continue; + + poly.Inflate( constraint.Value().Min() / 2, 16 ); + poly.Simplify( SHAPE_POLY_SET::PM_FAST ); + double resultingGlyphArea = poly.Area(); + + if( ( std::abs( resultingGlyphArea - glyphArea ) / glyphArea ) > 0.1 ) + { + collapsedArea = true; + break; + } + } + + if( collapsedStroke || collapsedArea ) + { + auto drcItem = DRC_ITEM::Create( DRCE_TEXT_THICKNESS ); + wxString msg; + + msg = _( "(TrueType font characters with insufficient stroke weight)" ); + + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); + drcItem->SetItems( item ); + drcItem->SetViolatingRule( constraint.GetParentRule() ); + + reportViolation( drcItem, item->GetPosition(), item->GetLayer() ); + } + } + else + { + int actualThickness = text->GetEffectiveTextPenWidth(); + + if( constraint.Value().HasMin() && actualThickness < constraint.Value().Min() ) + { + std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_TEXT_THICKNESS ); + wxString msg; + + msg.Printf( _( "(%s min thickness %s; actual %s)" ), + constraint.GetName(), + MessageTextFromValue( userUnits(), constraint.Value().Min() ), + MessageTextFromValue( userUnits(), actualThickness ) ); + + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); + drcItem->SetItems( item ); + drcItem->SetViolatingRule( constraint.GetParentRule() ); + + reportViolation( drcItem, item->GetPosition(), item->GetLayer() ); + } + + if( constraint.Value().HasMax() && actualThickness > constraint.Value().Max() ) + { + std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_TEXT_THICKNESS ); + wxString msg; + + msg.Printf( _( "(%s max thickness %s; actual %s)" ), + constraint.GetName(), + MessageTextFromValue( userUnits(), constraint.Value().Max() ), + MessageTextFromValue( userUnits(), actualThickness ) ); + + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); + drcItem->SetItems( item ); + drcItem->SetViolatingRule( constraint.GetParentRule() ); + + reportViolation( drcItem, item->GetPosition(), item->GetLayer() ); + } + } + + return true; + }; + + static const std::vector itemTypes = { PCB_TEXT_T, PCB_FP_TEXT_T, + PCB_TEXTBOX_T, PCB_FP_TEXTBOX_T }; + + forEachGeometryItem( itemTypes, LSET::AllLayersMask(), + [&]( BOARD_ITEM* item ) -> bool + { + ++count; + return true; + } ); + + forEachGeometryItem( itemTypes, LSET::AllLayersMask(), + [&]( BOARD_ITEM* item ) -> bool + { + if( !reportProgress( ii++, count, delta ) ) + return false; + + EDA_TEXT* text = nullptr; + int strikes = 0; + + switch( item->Type() ) + { + case PCB_TEXT_T: text = static_cast( item ); break; + case PCB_TEXTBOX_T: text = static_cast( item ); break; + case PCB_FP_TEXT_T: text = static_cast( item ); break; + case PCB_FP_TEXTBOX_T: text = static_cast( item ); break; + default: UNIMPLEMENTED_FOR( item->GetClass() ); break; + } + + if( !text || !text->IsVisible() ) + return true; + + if( m_drcEngine->IsErrorLimitExceeded( DRCE_TEXT_THICKNESS ) ) + strikes++; + else + checkTextThickness( item, text ); + + if( m_drcEngine->IsErrorLimitExceeded( DRCE_TEXT_HEIGHT ) ) + strikes++; + else + checkTextHeight( item, text ); + + if( strikes >= 2 ) + return false; + + return true; + } ); + + reportRuleStatistics(); + + return !m_drcEngine->IsCancelled(); +} + + +namespace detail +{ +static DRC_REGISTER_TEST_PROVIDER dummy; +} diff --git a/pcbnew/drc/drc_test_provider_track_width.cpp b/pcbnew/drc/drc_test_provider_track_width.cpp index da4f9077b4..e2098e4322 100644 --- a/pcbnew/drc/drc_test_provider_track_width.cpp +++ b/pcbnew/drc/drc_test_provider_track_width.cpp @@ -128,23 +128,24 @@ bool DRC_TEST_PROVIDER_TRACK_WIDTH::Run() if( fail_min || fail_max ) { std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_TRACK_WIDTH ); + wxString msg; if( fail_min ) { - m_msg.Printf( _( "(%s min width %s; actual %s)" ), + msg.Printf( _( "(%s min width %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), constraintWidth ), MessageTextFromValue( userUnits(), actual ) ); } else { - m_msg.Printf( _( "(%s max width %s; actual %s)" ), + msg.Printf( _( "(%s max width %s; actual %s)" ), constraint.GetName(), MessageTextFromValue( userUnits(), constraintWidth ), MessageTextFromValue( userUnits(), actual ) ); } - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); drcItem->SetItems( item ); drcItem->SetViolatingRule( constraint.GetParentRule() ); diff --git a/pcbnew/drc/drc_test_provider_via_diameter.cpp b/pcbnew/drc/drc_test_provider_via_diameter.cpp index 0006734faf..dec80a875c 100644 --- a/pcbnew/drc/drc_test_provider_via_diameter.cpp +++ b/pcbnew/drc/drc_test_provider_via_diameter.cpp @@ -114,26 +114,27 @@ bool DRC_TEST_PROVIDER_VIA_DIAMETER::Run() constraintDiameter = constraint.Value().Max(); } - if( fail_min ) - { - m_msg.Printf( _( "(%s min diameter %s; actual %s)" ), - constraint.GetName(), - MessageTextFromValue( userUnits(), constraintDiameter ), - MessageTextFromValue( userUnits(), actual ) ); - } - else if( fail_max ) - { - m_msg.Printf( _( "(%s max diameter %s; actual %s)" ), - constraint.GetName(), - MessageTextFromValue( userUnits(), constraintDiameter ), - MessageTextFromValue( userUnits(), actual ) ); - } - if( fail_min || fail_max ) { std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_VIA_DIAMETER ); + wxString msg; - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); + if( fail_min ) + { + msg.Printf( _( "(%s min diameter %s; actual %s)" ), + constraint.GetName(), + MessageTextFromValue( userUnits(), constraintDiameter ), + MessageTextFromValue( userUnits(), actual ) ); + } + else if( fail_max ) + { + msg.Printf( _( "(%s max diameter %s; actual %s)" ), + constraint.GetName(), + MessageTextFromValue( userUnits(), constraintDiameter ), + MessageTextFromValue( userUnits(), actual ) ); + } + + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); drcItem->SetItems( item ); drcItem->SetViolatingRule( constraint.GetParentRule() ); diff --git a/pcbnew/drc/drc_test_provider_zone_connections.cpp b/pcbnew/drc/drc_test_provider_zone_connections.cpp new file mode 100644 index 0000000000..8f4db972ea --- /dev/null +++ b/pcbnew/drc/drc_test_provider_zone_connections.cpp @@ -0,0 +1,203 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2021-2022 KiCad Developers. + * + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + This loads some rule resolvers for the ZONE_FILLER, and checks that pad thermal relief + connections have at least the required number of spokes. + + Errors generated: + - DRCE_STARVED_THERMAL +*/ + +class DRC_TEST_PROVIDER_ZONE_CONNECTIONS : public DRC_TEST_PROVIDER +{ +public: + DRC_TEST_PROVIDER_ZONE_CONNECTIONS() + { + } + + virtual ~DRC_TEST_PROVIDER_ZONE_CONNECTIONS() + { + } + + virtual bool Run() override; + + virtual const wxString GetName() const override + { + return wxT( "zone connections" ); + }; + + virtual const wxString GetDescription() const override + { + return wxT( "Checks thermal reliefs for a sufficient number of connecting spokes" ); + } +}; + +bool DRC_TEST_PROVIDER_ZONE_CONNECTIONS::Run() +{ + const int delta = 5; // This is the number of tests between 2 calls to the progress bar + int ii = 0; + + BOARD* board = m_drcEngine->GetBoard(); + BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings(); + std::shared_ptr connectivity = board->GetConnectivity(); + DRC_CONSTRAINT constraint; + std::vector zones; + + if( !reportPhase( _( "Checking thermal reliefs..." ) ) ) + return false; // DRC cancelled + + for( ZONE* zone : board->Zones() ) + zones.push_back( zone ); + + for( FOOTPRINT* footprint : board->Footprints() ) + { + for( ZONE* zone : footprint->Zones() ) + zones.push_back( zone ); + } + + for( ZONE* zone : zones ) + { + if( !reportProgress( ii++, zones.size(), delta ) ) + return false; + + for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() ) + { + const std::shared_ptr& zoneFill = zone->GetFilledPolysList( layer ); + + for( FOOTPRINT* footprint : board->Footprints() ) + { + for( PAD* pad : footprint->Pads() ) + { + if( m_drcEngine->IsErrorLimitExceeded( DRCE_STARVED_THERMAL ) ) + return true; + + if( m_drcEngine->IsCancelled() ) + return false; + + // Quick tests for "connected": + // + if( !pad->FlashLayer( layer ) ) + continue; + + if( pad->GetNetCode() != zone->GetNetCode() || pad->GetNetCode() <= 0 ) + continue; + + EDA_RECT item_boundingbox = pad->GetBoundingBox(); + + if( !item_boundingbox.Intersects( zone->GetCachedBoundingBox() ) ) + continue; + + // If those passed, do a thorough test: + // + constraint = bds.m_DRCEngine->EvalZoneConnection( pad, zone, layer ); + ZONE_CONNECTION conn = constraint.m_ZoneConnection; + + if( conn != ZONE_CONNECTION::THERMAL ) + continue; + + constraint = bds.m_DRCEngine->EvalRules( MIN_RESOLVED_SPOKES_CONSTRAINT, + pad, zone, layer ); + int minCount = constraint.m_Value.Min(); + + if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE || minCount <= 0 ) + continue; + + SHAPE_POLY_SET padPoly; + pad->TransformShapeWithClearanceToPolygon( padPoly, layer, 0, ARC_LOW_DEF, + ERROR_OUTSIDE ); + + SHAPE_LINE_CHAIN& padOutline = padPoly.Outline( 0 ); + std::vector intersections; + int spokes = 0; + + for( int jj = 0; jj < zoneFill->OutlineCount(); ++jj ) + padOutline.Intersect( zoneFill->Outline( jj ), intersections, true ); + + spokes += intersections.size() / 2; + + if( spokes <= 0 ) + continue; + + // Now we know we're connected, so see if there are any other manual spokes + // added: + // + for( PCB_TRACK* track : connectivity->GetConnectedTracks( pad ) ) + { + if( padOutline.PointInside( track->GetStart() ) ) + { + if( zone->GetFilledPolysList( layer )->Collide( track->GetEnd() ) ) + spokes++; + } + else if( padOutline.PointInside( track->GetEnd() ) ) + { + if( zone->GetFilledPolysList( layer )->Collide( track->GetStart() ) ) + spokes++; + } + } + + // And finally report it if there aren't enough: + // + if( spokes < minCount ) + { + std::shared_ptr drce = DRC_ITEM::Create( DRCE_STARVED_THERMAL ); + wxString msg; + + msg.Printf( _( "(%s min spoke count %d; actual %d)" ), + constraint.GetName(), + minCount, + spokes ); + + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); + drce->SetItems( zone, pad ); + drce->SetViolatingRule( constraint.GetParentRule() ); + + reportViolation( drce, pad->GetPosition(), UNDEFINED_LAYER ); + } + } + } + } + } + + return !m_drcEngine->IsCancelled(); +} + + +namespace detail +{ +static DRC_REGISTER_TEST_PROVIDER dummy; +}