ADDED assertion constraints for user-defined DRC checks.

This commit is contained in:
Jeff Young 2021-09-22 22:20:18 +01:00
parent f7721dd274
commit b7e196b710
11 changed files with 302 additions and 38 deletions

View File

@ -1,4 +1,5 @@
annular_width
assertion
board_edge
buried_via
clearance

View File

@ -292,6 +292,7 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
else if( sexprs.top() == "constraint" )
{
tokens = "annular_width|"
"assertion|"
"clearance|"
"courtyard_clearance|"
"diff_pair_gap|"
@ -330,13 +331,11 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
}
else if( sexprs.top() == "zone_connection" )
{
tokens = "solid "
"thermal_relief "
"none";
tokens = "none|solid|thermal_relief";
}
else if( sexprs.top() == "min_resolved_spokes" )
{
tokens = "0 1 2 3 4";
tokens = "0|1|2|3|4";
}
else if( sexprs.top() == "layer" )
{
@ -346,15 +345,14 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
{
tokens = "warning|error|ignore|exclusion";
}
else if( sexprs.top() == "severity" )
{
tokens = "error "
"exclusion "
"ignore "
"warning";
}
}
else if( context == STRING && !sexprs.empty() && sexprs.top() == "condition" )
else if( context == SEXPR_STRING && !sexprs.empty()
&& ( sexprs.top() == "condition" || sexprs.top() == "assertion" ) )
{
m_textEditor->AddText( "\"" );
}
else if( context == STRING && !sexprs.empty()
&& ( sexprs.top() == "condition" || sexprs.top() == "assertion" ) )
{
if( expr_context == STRUCT_REF )
{

View File

@ -194,25 +194,25 @@ For the latter use a `(layer "layer_name")` clause in the rule.
(rule "Distance between Vias of Different Nets"
(constraint hole_to_hole (min 0.254mm))
(condition "A.Type =='Via' && B.Type =='Via' && A.Net != B.Net"))
(condition "A.Type == 'Via' && B.Type == 'Via' && A.Net != B.Net"))
(rule "Clearance between Pads of Different Nets"
(constraint clearance (min 3.0mm))
(condition "A.Type =='Pad' && B.Type =='Pad' && A.Net != B.Net"))
(condition "A.Type == 'Pad' && B.Type == 'Pad' && A.Net != B.Net"))
(rule "Via Hole to Track Clearance"
(constraint hole_clearance (min 0.254mm))
(condition "A.Type =='Via' && B.Type =='Track'"))
(condition "A.Type == 'Via' && B.Type == 'Track'"))
(rule "Pad to Track Clearance"
(constraint clearance (min 0.2mm))
(condition "A.Type =='Pad' && B.Type =='Track'"))
(condition "A.Type == 'Pad' && B.Type == 'Track'"))
(rule "clearance-to-1mm-cutout"
(constraint clearance (min 0.8mm))
(condition "A.Layer=='Edge.Cuts' && A.Thickness == 1.0mm"))
(condition "A.Layer == 'Edge.Cuts' && A.Thickness == 1.0mm"))
(rule "Max Drill Hole Size Mechanical"
@ -247,4 +247,10 @@ For the latter use a `(layer "layer_name")` clause in the rule.
# Prevent solder wicking from SMD pads
(rule holes_in_pads
(constraint mechanical_hole_clearance (min 0.2mm))
(condition "B.Pad_Type == 'SMD'"))
(condition "B.Pad_Type == 'SMD'"))
# Disallow solder mask margin overrides
(rule "disallow solder mask margin overrides"
(constraint assertion "A.Soldermask_Margin_Override == 0mm")
(condition "A.Type == 'Pad'"))

View File

@ -871,6 +871,18 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO
}
}
auto testAssertion =
[&]( const DRC_ENGINE_CONSTRAINT* c )
{
REPORT( wxString::Format( _( "Checking assertion \"%s\"." ),
EscapeHTML( c->constraint.m_Test->GetExpression() ) ) )
if( c->constraint.m_Test->EvaluateFor( a, b, aLayer, aReporter ) )
REPORT( _( "Assertion passed." ) )
else
REPORT( EscapeHTML( _( "--> Assertion failed. <--" ) ) )
};
auto processConstraint =
[&]( const DRC_ENGINE_CONSTRAINT* c ) -> bool
{
@ -1027,8 +1039,7 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO
}
else if( c->parentRule )
{
REPORT( wxString::Format( _( "Rule layer '%s' not matched; rule "
"ignored." ),
REPORT( wxString::Format( _( "Rule layer '%s' not matched; rule ignored." ),
EscapeHTML( c->parentRule->m_LayerSource ) ) )
}
else
@ -1061,8 +1072,22 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO
if( !c->condition || c->condition->GetExpression().IsEmpty() )
{
REPORT( implicit ? _( "Unconditional constraint applied." )
: _( "Unconditional rule applied." ) );
if( aReporter )
{
if( implicit )
{
REPORT( _( "Unconditional constraint applied." ) )
}
else if( constraint.m_Type == ASSERTION_CONSTRAINT )
{
REPORT( _( "Unconditional rule applied." ) )
testAssertion( c );
}
else
{
REPORT( _( "Unconditional rule applied; overrides previous constraints." ) )
}
}
constraint = c->constraint;
return true;
@ -1081,8 +1106,22 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO
if( c->condition->EvaluateFor( a, b, aLayer, aReporter ) )
{
REPORT( implicit ? _( "Constraint applied." )
: _( "Rule applied; overrides previous constraints." ) )
if( aReporter )
{
if( implicit )
{
REPORT( _( "Constraint applied." ) )
}
else if( constraint.m_Type == ASSERTION_CONSTRAINT )
{
REPORT( _( "Rule applied." ) )
testAssertion( c );
}
else
{
REPORT( _( "Rule applied; overrides previous constraints." ) )
}
}
if( c->constraint.m_Value.HasMin() )
constraint.m_Value.SetMin( c->constraint.m_Value.Min() );
@ -1239,6 +1278,78 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO
return constraint;
}
void DRC_ENGINE::ProcessAssertions( const BOARD_ITEM* a,
std::function<void( const DRC_CONSTRAINT* )> aFailureHandler,
REPORTER* aReporter )
{
/*
* NOTE: all string manipulation MUST BE KEPT INSIDE the REPORT macro. It absolutely
* kills performance when running bulk DRC tests (where aReporter is nullptr).
*/
auto testAssertion =
[&]( const DRC_ENGINE_CONSTRAINT* c )
{
REPORT( wxString::Format( _( "Checking rule assertion \"%s\"." ),
EscapeHTML( c->constraint.m_Test->GetExpression() ) ) )
if( c->constraint.m_Test->EvaluateFor( a, nullptr, a->GetLayer(), aReporter ) )
{
REPORT( _( "Assertion passed." ) )
}
else
{
REPORT( EscapeHTML( _( "--> Assertion failed. <--" ) ) )
aFailureHandler( &c->constraint );
}
};
auto processConstraint =
[&]( const DRC_ENGINE_CONSTRAINT* c )
{
REPORT( "" )
REPORT( wxString::Format( _( "Checking %s." ), c->constraint.GetName() ) )
if( !( a->GetLayerSet() & c->layerTest ).any() )
{
REPORT( wxString::Format( _( "Rule layer '%s' not matched; rule ignored." ),
EscapeHTML( c->parentRule->m_LayerSource ) ) )
}
if( !c->condition || c->condition->GetExpression().IsEmpty() )
{
REPORT( _( "Unconditional rule applied." ) )
testAssertion( c );
}
else
{
REPORT( wxString::Format( _( "Checking rule condition \"%s\"." ),
EscapeHTML( c->condition->GetExpression() ) ) )
if( c->condition->EvaluateFor( a, nullptr, a->GetLayer(), aReporter ) )
{
REPORT( _( "Rule applied." ) )
testAssertion( c );
}
else
{
REPORT( _( "Condition not satisfied; rule ignored." ) )
}
}
};
if( m_constraintMap.count( ASSERTION_CONSTRAINT ) )
{
std::vector<DRC_ENGINE_CONSTRAINT*>* ruleset = m_constraintMap[ ASSERTION_CONSTRAINT ];
for( int ii = 0; ii < (int) ruleset->size(); ++ii )
processConstraint( ruleset->at( ii ) );
}
}
#undef REPORT
#undef UNITS
#undef REPORT_VALUE

View File

@ -152,6 +152,10 @@ public:
DRC_CONSTRAINT EvalZoneConnection( const BOARD_ITEM* a, const BOARD_ITEM* b,
PCB_LAYER_ID aLayer, REPORTER* aReporter = nullptr );
void ProcessAssertions( const BOARD_ITEM* a,
std::function<void( const DRC_CONSTRAINT* )> aFailureHandler,
REPORTER* aReporter = nullptr );
bool HasRulesForConstraintType( DRC_CONSTRAINT_T constraintID );
EDA_UNITS UserUnits() const { return m_userUnits; }

View File

@ -183,6 +183,10 @@ DRC_ITEM DRC_ITEM::unresolvedVariable( DRCE_UNRESOLVED_VARIABLE,
_( "Unresolved text variable" ),
wxT( "unresolved_variable" ) );
DRC_ITEM DRC_ITEM::assertionFailure( DRCE_ASSERTION_FAILURE,
_( "Assertion failure" ),
wxT( "assertion_failure" ) );
DRC_ITEM DRC_ITEM::copperSliver( DRCE_COPPER_SLIVER,
_( "Copper sliver" ),
wxT( "copper_sliver" ) );
@ -334,6 +338,7 @@ std::shared_ptr<DRC_ITEM> DRC_ITEM::Create( int aErrorCode )
case DRCE_EXTRA_FOOTPRINT: return std::make_shared<DRC_ITEM>( extraFootprint );
case DRCE_LIB_FOOTPRINT_ISSUES: return std::make_shared<DRC_ITEM>( libFootprintIssues );
case DRCE_UNRESOLVED_VARIABLE: return std::make_shared<DRC_ITEM>( unresolvedVariable );
case DRCE_ASSERTION_FAILURE: return std::make_shared<DRC_ITEM>( assertionFailure );
case DRCE_COPPER_SLIVER: return std::make_shared<DRC_ITEM>( copperSliver );
case DRCE_OVERLAPPING_SILK: return std::make_shared<DRC_ITEM>( silkOverlaps );
case DRCE_SILK_CLEARANCE: return std::make_shared<DRC_ITEM>( silkClearance );

View File

@ -73,6 +73,7 @@ enum PCB_DRC_CODE {
DRCE_PAD_TH_WITH_NO_HOLE, // footprint has Plated Through-Hole with no hole
DRCE_UNRESOLVED_VARIABLE,
DRCE_ASSERTION_FAILURE, // user-defined (custom rule) assertion
DRCE_COPPER_SLIVER,
DRCE_SOLDERMASK_BRIDGE, // failure to maintain min soldermask web thickness
@ -177,6 +178,7 @@ private:
static DRC_ITEM netConflict;
static DRC_ITEM libFootprintIssues;
static DRC_ITEM unresolvedVariable;
static DRC_ITEM assertionFailure;
static DRC_ITEM copperSliver;
static DRC_ITEM silkClearance;
static DRC_ITEM solderMaskBridge;

View File

@ -67,7 +67,8 @@ enum DRC_CONSTRAINT_T
DIFF_PAIR_INTRA_SKEW_CONSTRAINT,
VIA_COUNT_CONSTRAINT,
MECHANICAL_CLEARANCE_CONSTRAINT,
MECHANICAL_HOLE_CLEARANCE_CONSTRAINT
MECHANICAL_HOLE_CLEARANCE_CONSTRAINT,
ASSERTION_CONSTRAINT
};
@ -123,6 +124,7 @@ class DRC_CONSTRAINT
m_Value(),
m_DisallowFlags( 0 ),
m_ZoneConnection( ZONE_CONNECTION::INHERITED ),
m_Test( nullptr ),
m_name( aName ),
m_parentRule( nullptr )
{
@ -163,14 +165,15 @@ class DRC_CONSTRAINT
}
public:
DRC_CONSTRAINT_T m_Type;
MINOPTMAX<int> m_Value;
int m_DisallowFlags;
ZONE_CONNECTION m_ZoneConnection;
DRC_CONSTRAINT_T m_Type;
MINOPTMAX<int> m_Value;
int m_DisallowFlags;
ZONE_CONNECTION m_ZoneConnection;
DRC_RULE_CONDITION* m_Test;
private:
wxString m_name; // For just-in-time constraints
DRC_RULE* m_parentRule; // For constraints found in rules
wxString m_name; // For just-in-time constraints
DRC_RULE* m_parentRule; // For constraints found in rules
};

View File

@ -260,7 +260,7 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule )
if( (int) token == DSN_RIGHT || token == T_EOF )
{
msg.Printf( _( "Missing constraint type.| Expected %s." ),
"clearance, hole_clearance, edge_clearance, mechanical_clearance, "
"assertion, clearance, hole_clearance, edge_clearance, mechanical_clearance, "
"mechanical_hole_clearance, courtyard_clearance, silk_clearance, hole_size, "
"hole_to_hole, track_width, annular_width, via_diameter, disallow, "
"zone_connection, thermal_relief_gap, thermal_spoke_width, min_resolved_spokes, "
@ -271,6 +271,7 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule )
switch( token )
{
case T_assertion: c.m_Type = ASSERTION_CONSTRAINT; break;
case T_clearance: c.m_Type = CLEARANCE_CONSTRAINT; break;
case T_hole_clearance: c.m_Type = HOLE_CLEARANCE_CONSTRAINT; break;
case T_edge_clearance: c.m_Type = EDGE_CLEARANCE_CONSTRAINT; break;
@ -298,7 +299,7 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule )
case T_mechanical_hole_clearance: c.m_Type = MECHANICAL_HOLE_CLEARANCE_CONSTRAINT; break;
default:
msg.Printf( _( "Unrecognized item '%s'.| Expected %s." ), FromUTF8(),
"clearance, hole_clearance, edge_clearance, mechanical_clearance, "
"assertion, clearance, hole_clearance, edge_clearance, mechanical_clearance, "
"mechanical_hole_clearance, courtyard_clearance, silk_clearance, hole_size, "
"hole_to_hole, track_width, annular_width, disallow, zone_connection, "
"thermal_relief_gap, thermal_spoke_width, min_resolved_spokes, length, skew, "
@ -412,6 +413,33 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule )
aRule->AddConstraint( c );
return;
}
else if( c.m_Type == ASSERTION_CONSTRAINT )
{
token = NextTok();
if( (int) token == DSN_RIGHT )
reportError( _( "Missing assertion expression." ) );
if( IsSymbol( token ) )
{
c.m_Test = new DRC_RULE_CONDITION( FromUTF8() );
c.m_Test->Compile( m_reporter, CurLineNumber(), CurOffset() );
}
else
{
msg.Printf( _( "Unrecognized item '%s'.| Expected quoted expression." ), FromUTF8() );
reportError( msg );
}
if( (int) NextTok() != DSN_RIGHT )
{
reportError( wxString::Format( _( "Unrecognized item '%s'." ), FromUTF8() ) );
parseUnknown();
}
aRule->AddConstraint( c );
return;
}
for( token = NextTok(); token != T_RIGHT && token != T_EOF; token = NextTok() )
{

View File

@ -1,7 +1,7 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2004-2020 KiCad Developers.
* Copyright (C) 2004-2021 KiCad Developers.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -25,6 +25,7 @@
#include <drc/drc_engine.h>
#include <drc/drc_item.h>
#include <drc/drc_rule.h>
#include <drc/drc_rule_condition.h>
#include <drc/drc_test_provider.h>
#include <pad.h>
#include <pcb_track.h>
@ -38,6 +39,7 @@
- DRCE_DISABLED_LAYER_ITEM, ///< item on a disabled layer
- DRCE_INVALID_OUTLINE, ///< invalid board outline
- DRCE_UNRESOLVED_VARIABLE,
- DRCE_ASSERTION_FAILURE ///< user-defined assertions
*/
class DRC_TEST_PROVIDER_MISC : public DRC_TEST_PROVIDER
@ -69,6 +71,7 @@ private:
void testOutline();
void testDisabledLayers();
void testTextVars();
void testAssertions();
BOARD* m_board;
};
@ -119,6 +122,19 @@ void DRC_TEST_PROVIDER_MISC::testOutline()
void DRC_TEST_PROVIDER_MISC::testDisabledLayers()
{
// This is the number of tests between 2 calls to the progress bar
const int delta = 2000;
int ii = 0;
int items = 0;
auto countItems =
[&]( BOARD_ITEM* item ) -> bool
{
++items;
return true;
};
LSET disabledLayers = m_board->GetEnabledLayers().flip();
// Perform the test only for copper layers
@ -127,6 +143,12 @@ void DRC_TEST_PROVIDER_MISC::testDisabledLayers()
auto checkDisabledLayers =
[&]( BOARD_ITEM* item ) -> bool
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_DISABLED_LAYER_ITEM ) )
return false;
if( !reportProgress( ii++, items, delta ) )
return false;
PCB_LAYER_ID badLayer = UNDEFINED_LAYER;
if( item->Type() == PCB_PAD_T )
@ -172,7 +194,7 @@ void DRC_TEST_PROVIDER_MISC::testDisabledLayers()
if( badLayer != UNDEFINED_LAYER )
{
std::shared_ptr<DRC_ITEM>drcItem = DRC_ITEM::Create( DRCE_DISABLED_LAYER_ITEM );
auto drcItem = DRC_ITEM::Create( DRCE_DISABLED_LAYER_ITEM );
m_msg.Printf( _( "(layer %s)" ), LayerName( badLayer ) );
@ -185,18 +207,78 @@ void DRC_TEST_PROVIDER_MISC::testDisabledLayers()
return true;
};
forEachGeometryItem( s_allBasicItems, LSET::AllLayersMask(), countItems );
forEachGeometryItem( s_allBasicItems, LSET::AllLayersMask(), checkDisabledLayers );
}
void DRC_TEST_PROVIDER_MISC::testAssertions()
{
// This is the number of tests between 2 calls to the progress bar
const int delta = 2000;
int ii = 0;
int items = 0;
auto countItems =
[&]( BOARD_ITEM* item ) -> bool
{
++items;
return true;
};
auto checkAssertions =
[&]( BOARD_ITEM* item ) -> bool
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_ASSERTION_FAILURE ) )
return false;
if( !reportProgress( ii++, items, delta ) )
return false;
m_drcEngine->ProcessAssertions( item,
[&]( const DRC_CONSTRAINT* c )
{
auto drcItem = DRC_ITEM::Create( DRCE_ASSERTION_FAILURE );
drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " (" )
+ c->GetName() + wxS( ")" ) );
drcItem->SetItems( item );
reportViolation( drcItem, item->GetPosition() );
} );
return true;
};
forEachGeometryItem( {}, LSET::AllLayersMask(), countItems );
forEachGeometryItem( {}, LSET::AllLayersMask(), checkAssertions );
}
void DRC_TEST_PROVIDER_MISC::testTextVars()
{
auto checkUnresolvedTextVar =
// This is the number of tests between 2 calls to the progress bar
const int delta = 2000;
int ii = 0;
int items = 0;
auto countItems =
[&]( BOARD_ITEM* item ) -> bool
{
++items;
return true;
};
auto checkTextVars =
[&]( EDA_ITEM* item ) -> bool
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_UNRESOLVED_VARIABLE ) )
return false;
if( !reportProgress( ii++, items, delta ) )
return false;
EDA_TEXT* text = dynamic_cast<EDA_TEXT*>( item );
if( text && text->GetShownText().Matches( wxT( "*${*}*" ) ) )
@ -209,8 +291,8 @@ void DRC_TEST_PROVIDER_MISC::testTextVars()
return true;
};
forEachGeometryItem( { PCB_FP_TEXT_T, PCB_TEXT_T }, LSET::AllLayersMask(),
checkUnresolvedTextVar );
forEachGeometryItem( { PCB_FP_TEXT_T, PCB_TEXT_T }, LSET::AllLayersMask(), countItems );
forEachGeometryItem( { PCB_FP_TEXT_T, PCB_TEXT_T }, LSET::AllLayersMask(), checkTextVars );
DS_PROXY_VIEW_ITEM* drawingSheet = m_drcEngine->GetDrawingSheet();
DS_DRAW_ITEM_LIST drawItems;
@ -273,6 +355,14 @@ bool DRC_TEST_PROVIDER_MISC::Run()
testTextVars();
}
if( !m_drcEngine->IsErrorLimitExceeded( DRCE_ASSERTION_FAILURE ) )
{
if( !reportPhase( _( "Checking assertions..." ) ) )
return false; // DRC cancelled
testAssertions();
}
return true;
}

View File

@ -840,6 +840,22 @@ int BOARD_INSPECTION_TOOL::InspectConstraints( const TOOL_EVENT& aEvent )
r->Flush();
r = m_inspectConstraintsDialog->AddPage( _( "Assertions" ) );
reportHeader( _( "Assertions for:" ), item, r );
if( compileError )
reportCompileError( r );
if( courtyardError )
{
r->Report( "" );
r->Report( _( "Report may be incomplete: some footprint courtyards are malformed." )
+ " <a href='drc'>" + _( "Run DRC for a full analysis." ) + "</a>" );
}
drcEngine.ProcessAssertions( item, []( const DRC_CONSTRAINT* c ){}, r );
r->Flush();
m_inspectConstraintsDialog->FinishInitialization();
m_inspectConstraintsDialog->Raise();
m_inspectConstraintsDialog->Show( true );