ADDED Clearance Inspector.

This commit is contained in:
Jeff Young 2020-09-10 20:58:05 +01:00
parent e31705d4b3
commit fc1665ff28
12 changed files with 335 additions and 47 deletions

View File

@ -81,7 +81,7 @@ NETCLASS* BOARD_CONNECTED_ITEM::GetEffectiveNetclass() const
* LEVEL 3: Accumulated local settings, netclass settings, & board design settings
*/
int BOARD_CONNECTED_ITEM::GetClearance( PCB_LAYER_ID aLayer, BOARD_ITEM* aItem,
wxString* aSource ) const
wxString* aSource, REPORTER* aReporter ) const
{
BOARD* board = GetBoard();
int clearance = 0;
@ -111,7 +111,7 @@ int BOARD_CONNECTED_ITEM::GetClearance( PCB_LAYER_ID aLayer, BOARD_ITEM* aItem,
// LEVEL 2: Rules
//
if( GetRuleClearance( aItem, aLayer, &clearance, aSource ) )
if( GetRuleClearance( aItem, aLayer, &clearance, aSource, aReporter ) )
return clearance;
// LEVEL 3: Accumulated local settings, netclass settings, & board design settings
@ -153,10 +153,11 @@ int BOARD_CONNECTED_ITEM::GetClearance( PCB_LAYER_ID aLayer, BOARD_ITEM* aItem,
bool BOARD_CONNECTED_ITEM::GetRuleClearance( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer,
int* aClearance, wxString* aSource ) const
int* aClearance, wxString* aSource,
REPORTER* aReporter ) const
{
const DRC_CONSTRAINT* constraint = GetConstraint( this, aItem, DRC_RULE_ID_CLEARANCE, aLayer,
aSource );
aSource, aReporter );
if( constraint )
{
@ -164,6 +165,13 @@ bool BOARD_CONNECTED_ITEM::GetRuleClearance( BOARD_ITEM* aItem, PCB_LAYER_ID aLa
*aSource = wxString::Format( _( "'%s' rule" ), *aSource );
*aClearance = constraint->m_Value.Min();
if( aReporter )
{
wxString clearance = StringFromValue( aReporter->GetUnits(), *aClearance, true );
aReporter->Report( wxString::Format( _( "Clearance: %s." ), clearance ) );
}
return true;
}

View File

@ -28,6 +28,7 @@
#include <class_board_item.h>
#include <netinfo.h>
#include <reporter.h>
#include <kicad_string.h>
class NETCLASS;
@ -167,7 +168,7 @@ public:
* @return int - the clearance in internal units.
*/
virtual int GetClearance( PCB_LAYER_ID aLayer, BOARD_ITEM* aItem = nullptr,
wxString* aSource = nullptr ) const;
wxString* aSource = nullptr, REPORTER* aReporter = nullptr ) const;
/**
* Function GetRuleClearance
@ -178,7 +179,7 @@ public:
* @return true if a rule was fired
*/
virtual bool GetRuleClearance( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int* aClearance,
wxString* aSource ) const;
wxString* aSource, REPORTER* aReporter = nullptr ) const;
/**
* Function GetLocalClearanceOverrides

View File

@ -31,7 +31,8 @@
const DRC_CONSTRAINT* GetConstraint( const BOARD_ITEM* aItem, const BOARD_ITEM* bItem,
int aConstraint, PCB_LAYER_ID aLayer, wxString* aRuleName )
int aConstraint, PCB_LAYER_ID aLayer, wxString* aRuleName,
REPORTER* aReporter )
{
BOARD* board = aItem->GetBoard();
@ -40,33 +41,76 @@ const DRC_CONSTRAINT* GetConstraint( const BOARD_ITEM* aItem, const BOARD_ITEM*
for( DRC_RULE* rule : board->GetDesignSettings().m_DRCRules )
{
if( !rule->m_LayerCondition.test( aLayer ) )
continue;
for( const DRC_CONSTRAINT& constraint : rule->m_Constraints )
if( aReporter )
{
if( constraint.m_Type != aConstraint )
continue;
aReporter->Report( wxString::Format( _( "Checking rule \"%s\"." ),
rule->m_Name ) );
}
if( !rule->m_LayerCondition.test( aLayer ) )
{
if( aReporter )
{
aReporter->Report( wxString::Format( _( "Rule layer \"%s\" not matched." ),
rule->m_LayerSource ) );
aReporter->Report( "Rule not applied." );
}
continue;
}
const DRC_CONSTRAINT* constraint = nullptr;
for( const DRC_CONSTRAINT& candidate : rule->m_Constraints )
{
if( candidate.m_Type == aConstraint )
{
constraint = &candidate;
break;
}
}
if( aReporter && !constraint )
{
aReporter->Report( _( "Rule contains no applicable constraints." ) );
aReporter->Report( _( "Rule not applied." ) );
}
else
{
if( aReporter )
{
aReporter->Report( wxString::Format( _( "Checking rule condition \"%s\"." ),
rule->m_Condition.m_Expression ) );
}
if( rule->m_Condition.EvaluateFor( aItem, bItem, aLayer ) )
{
if( aReporter )
aReporter->Report( "Rule applied." );
if( aRuleName )
*aRuleName = rule->m_Name;
return &constraint;
return constraint;
}
if( bItem && rule->m_Condition.EvaluateFor( bItem, aItem, aLayer ) )
{
if( aReporter )
aReporter->Report( "Rule applied." );
if( aRuleName )
*aRuleName = rule->m_Name;
return &constraint;
return constraint;
}
if( aReporter )
aReporter->Report( "Condition not satisfied; rule not applied." );
}
if( aReporter )
aReporter->Report( "" );
}
return nullptr;
@ -97,21 +141,37 @@ DRC_RULE_CONDITION::~DRC_RULE_CONDITION()
bool DRC_RULE_CONDITION::EvaluateFor( const BOARD_ITEM* aItemA, const BOARD_ITEM* aItemB,
PCB_LAYER_ID aLayer )
PCB_LAYER_ID aLayer, REPORTER* aReporter )
{
// An unconditional rule is always true
if( m_Expression.IsEmpty() )
return true;
{
if( aReporter )
aReporter->Report( _( "Unconditional constraint." ) );
return true;
}
if( aReporter )
aReporter->Report( _( "Evaluating expression \"" + m_Expression + "\"." ) );
// A rule which failed to compile is always false
if( !m_ucode )
{
if( aReporter )
aReporter->Report( _( "ERROR in expression." ) );
return false;
}
BOARD_ITEM* a = const_cast<BOARD_ITEM*>( aItemA );
BOARD_ITEM* b = aItemB ? const_cast<BOARD_ITEM*>( aItemB ) : DELETED_BOARD_ITEM::GetInstance();
PCB_EXPR_CONTEXT ctx( aLayer );
ctx.SetItems( a, b );
ctx.SetErrorCallback( [&]( const wxString& aMessage, int aOffset )
{
if( aReporter )
aReporter->Report( _( "ERROR: " ) + aMessage );
} );
return m_ucode->Run( &ctx )->AsDouble() != 0.0;
}

View File

@ -88,8 +88,7 @@ class DRC_CONSTRAINT
public:
DRC_CONSTRAINT() :
m_Type( DRC_RULE_ID_UNKNOWN ),
m_DisallowFlags( 0 ),
m_LayerCondition( LSET::AllLayersMask() )
m_DisallowFlags( 0 )
{}
const MINOPTMAX<int>& GetValue() const { return m_Value; }
@ -99,7 +98,6 @@ public:
DRC_CONSTRAINT_TYPE_T m_Type;
MINOPTMAX<int> m_Value;
int m_DisallowFlags;
LSET m_LayerCondition;
};
@ -109,7 +107,8 @@ public:
DRC_RULE_CONDITION();
~DRC_RULE_CONDITION();
bool EvaluateFor( const BOARD_ITEM* aItemA, const BOARD_ITEM* aItemB, PCB_LAYER_ID aLayer );
bool EvaluateFor( const BOARD_ITEM* aItemA, const BOARD_ITEM* aItemB, PCB_LAYER_ID aLayer,
REPORTER* aReporter = nullptr );
bool Compile( REPORTER* aReporter, int aSourceLine, int aSourceOffset );
public:
@ -128,6 +127,7 @@ public:
public:
wxString m_Name;
wxString m_LayerSource;
LSET m_LayerCondition;
wxString m_TestProviderName;
DRC_RULE_CONDITION m_Condition;
@ -136,7 +136,9 @@ public:
const DRC_CONSTRAINT* GetConstraint( const BOARD_ITEM* aItem, const BOARD_ITEM* bItem,
int aConstraint, PCB_LAYER_ID aLayer, wxString* aRuleName );
int aConstraint, PCB_LAYER_ID aLayer,
wxString* aRuleName = nullptr,
REPORTER* aReporter = nullptr );
#endif // DRC_RULE_H

View File

@ -215,6 +215,7 @@ DRC_RULE* DRC_RULES_PARSER::parseDRC_RULE()
break;
case T_layer:
rule->m_LayerSource = FromUTF8();
rule->m_LayerCondition = parseLayer();
break;
@ -453,7 +454,7 @@ LSET DRC_RULES_PARSER::parseLayer()
}
if( !retVal.any() )
reportError( wxString::Format( _( "Unrecognized layer '%s' " ), layerName ) );
reportError( wxString::Format( _( "Unrecognized layer '%s'." ), layerName ) );
}
if( (int) NextTok() != DSN_RIGHT )

View File

@ -372,6 +372,7 @@ void PCB_EDIT_FRAME::ReCreateMenuBar()
inspectMenu->AppendSeparator();
inspectMenu->Add( PCB_ACTIONS::runDRC );
inspectMenu->Add( PCB_ACTIONS::inspectClearance );
//-- Tools menu ----------------------------------------------------------

View File

@ -204,23 +204,21 @@ bool EDIT_TOOL::Init()
auto specialToolsSubMenu = std::make_shared<SPECIAL_TOOLS_CONTEXT_MENU>( this );
menu.AddSeparator();
m_selectionTool->GetToolMenu().AddSubMenu( specialToolsSubMenu );
menu.AddMenu( specialToolsSubMenu.get(), SELECTION_CONDITIONS::NotEmpty );
menu.AddMenu( specialToolsSubMenu.get(), SELECTION_CONDITIONS::NotEmpty, 100 );
menu.AddSeparator();
menu.AddItem( ACTIONS::cut, SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( ACTIONS::copy, SELECTION_CONDITIONS::NotEmpty );
menu.AddSeparator( 150 );
menu.AddItem( ACTIONS::cut, SELECTION_CONDITIONS::NotEmpty, 150 );
menu.AddItem( ACTIONS::copy, SELECTION_CONDITIONS::NotEmpty, 150 );
// Selection tool handles the context menu for some other tools, such as the Picker.
// Don't add things like Paste when another tool is active.
menu.AddItem( ACTIONS::paste, noActiveToolCondition );
menu.AppendSeparator();
menu.AddItem( ACTIONS::selectAll, noItemsCondition );
menu.AddItem( ACTIONS::paste, noActiveToolCondition, 150 );
menu.AddItem( ACTIONS::selectAll, noItemsCondition, 150 );
// Footprint actions
menu.AddSeparator();
menu.AddItem( PCB_ACTIONS::editFpInFpEditor, singleModuleCondition );
menu.AddItem( PCB_ACTIONS::updateFootprint, singleModuleCondition );
menu.AddItem( PCB_ACTIONS::changeFootprint, singleModuleCondition );
menu.AddSeparator( 150 );
menu.AddItem( PCB_ACTIONS::editFpInFpEditor, singleModuleCondition, 150 );
menu.AddItem( PCB_ACTIONS::updateFootprint, singleModuleCondition, 150 );
menu.AddItem( PCB_ACTIONS::changeFootprint, singleModuleCondition, 150 );
return true;
}

View File

@ -1025,6 +1025,12 @@ TOOL_ACTION PCB_ACTIONS::boardStatistics( "pcbnew.InspectionTool.ShowStatisticsD
_( "Show Board Statistics" ), _( "Shows board statistics" ),
pcbnew_xpm );
TOOL_ACTION PCB_ACTIONS::inspectClearance( "pcbnew.InspectionTool.InspectClearance",
AS_GLOBAL, 0, "",
_( "Clearance Resolution..." ),
_( "Show clearance resolution for the active layer between two selected objects" ),
mw_add_gap_xpm );
//Geographic re-annotation tool
TOOL_ACTION PCB_ACTIONS::boardReannotate( "pcbnew.ReannotateTool.ShowReannotateDialog",
AS_GLOBAL, 0, "",

View File

@ -417,6 +417,7 @@ public:
static TOOL_ACTION boardStatistics;
static TOOL_ACTION boardReannotate;
static TOOL_ACTION repairBoard;
static TOOL_ACTION inspectClearance;
// Appearance controls
static TOOL_ACTION clearHighlight;

View File

@ -243,9 +243,10 @@ bool PCB_EDITOR_CONTROL::Init()
toolMenu.AddSubMenu( lockMenu );
toolMenu.AddSubMenu( groupMenu );
menu.AddMenu( groupMenu.get(), SELECTION_CONDITIONS::NotEmpty, 100 );
menu.AddMenu( lockMenu.get(), SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::LockableItems ), 100 );
menu.AddMenu( zoneMenu.get(), SELECTION_CONDITIONS::OnlyType( PCB_ZONE_AREA_T ), 200 );
menu.AddMenu( lockMenu.get(), SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::LockableItems ), 200 );
menu.AddMenu( groupMenu.get(), SELECTION_CONDITIONS::NotEmpty, 200 );
}
DRAWING_TOOL* drawingTool = m_toolMgr->GetTool<DRAWING_TOOL>();

View File

@ -30,6 +30,7 @@
#include <pcb_painter.h>
#include <connectivity/connectivity_data.h>
#include <profile.h>
#include <dialogs/wx_html_report_box.h>
#include "pcb_inspection_tool.h"
@ -77,7 +78,9 @@ bool PCB_INSPECTION_TOOL::Init()
CONDITIONAL_MENU& menu = selectionTool->GetToolMenu().GetMenu();
selectionTool->GetToolMenu().AddSubMenu( netSubMenu );
menu.AddMenu( netSubMenu.get(), SELECTION_CONDITIONS::OnlyTypes( connectedTypes ) );
menu.AddMenu( netSubMenu.get(), SELECTION_CONDITIONS::OnlyTypes( connectedTypes ), 200 );
menu.AddItem( PCB_ACTIONS::inspectClearance, SELECTION_CONDITIONS::Count( 2 ), 200 );
return true;
}
@ -97,6 +100,195 @@ int PCB_INSPECTION_TOOL::ShowStatisticsDialog( const TOOL_EVENT& aEvent )
}
void reportZoneConnection( ZONE_CONTAINER* aZone, D_PAD* aPad, REPORTER* r )
{
ENUM_MAP<ZONE_CONNECTION> connectionEnum = ENUM_MAP<ZONE_CONNECTION>::Instance();
wxString source;
ZONE_CONNECTION connection = aZone->GetPadConnection( aPad, &source );
r->Report( "" );
r->Report( wxString::Format( _( "Zone connection type: %s." ),
connectionEnum.ToString( aZone->GetPadConnection() ) ) );
if( source != _( "zone" ) )
{
r->Report( wxString::Format( _( "Overridden by %s; connection type: %s." ),
source,
connectionEnum.ToString( connection ) ) );
}
// Resolve complex connection types into simple types
if( connection == ZONE_CONNECTION::THT_THERMAL )
{
if( aPad->GetAttribute() == PAD_ATTRIB_STANDARD )
{
connection = ZONE_CONNECTION::THERMAL;
}
else
{
connection = ZONE_CONNECTION::FULL;
r->Report( wxString::Format( _( "Pad is not a PTH pad; connection will be: %s." ),
connectionEnum.ToString( ZONE_CONNECTION::FULL ) ) );
}
}
r->Report( "" );
// Process simple connection types
if( connection == ZONE_CONNECTION::THERMAL )
{
int gap = aZone->GetThermalReliefGap();
r->Report( wxString::Format( _( "Zone thermal relief: %s." ),
StringFromValue( r->GetUnits(), gap, true ) ) );
gap = aZone->GetThermalReliefGap( aPad, &source );
if( source != _( "zone" ) )
{
r->Report( wxString::Format( _( "Overridden by %s; thermal relief: %s." ),
source,
StringFromValue( r->GetUnits(), gap, true ) ) );
}
}
else if( connection == ZONE_CONNECTION::NONE )
{
int clearance = aZone->GetLocalClearance();
r->Report( wxString::Format( _( "Zone clearance: %s." ),
StringFromValue( r->GetUnits(), clearance, true ) ) );
if( aZone->GetThermalReliefGap( aPad ) > clearance )
{
clearance = aZone->GetThermalReliefGap( aPad, &source );
if( source != _( "zone" ) )
{
r->Report( wxString::Format( _( "Overridden by larger thermal relief from %s;"
"clearance: %s." ),
source,
StringFromValue( r->GetUnits(), clearance, true ) ) );
}
}
}
else
{
r->Report( _( "Clearance is 0." ) );
}
}
void reportCopperClearance( PCB_LAYER_ID aLayer, BOARD_CONNECTED_ITEM* aA, BOARD_ITEM* aB,
REPORTER* r )
{
wxString source;
r->Report( "" );
// JEY TODO: hook this up to new DRC engine to get "classic" sources as well; right now
// we're just reporting on rules....
aA->GetClearance( aLayer, aB, &source, r );
}
int PCB_INSPECTION_TOOL::InspectClearance( const TOOL_EVENT& aEvent )
{
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
const PCBNEW_SELECTION& selection = selTool->GetSelection();
PCB_LAYER_ID layer = m_frame->GetActiveLayer();
if( selection.Size() != 2 )
{
m_frame->ShowInfoBarError( _( "Select two items for a clearance resolution report." ) );
return 0;
}
if( m_inspectClearanceDialog == nullptr )
{
m_inspectClearanceDialog = std::make_unique<DIALOG_HTML_REPORTER>( m_frame );
m_inspectClearanceDialog->SetTitle( _( "Clearance Report" ) );
m_inspectClearanceDialog->Connect( wxEVT_CLOSE_WINDOW,
wxCommandEventHandler( PCB_INSPECTION_TOOL::onInspectClearanceDialogClosed ),
nullptr, this );
}
WX_HTML_REPORT_BOX* r = m_inspectClearanceDialog->m_Reporter;
r->SetUnits( m_frame->GetUserUnits() );
r->Clear();
BOARD_ITEM* a = static_cast<BOARD_ITEM*>( selection.GetItem( 0 ) );
BOARD_ITEM* b = static_cast<BOARD_ITEM*>( selection.GetItem( 1 ) );
if( a->Type() != PCB_ZONE_AREA_T && b->Type() == PCB_ZONE_AREA_T )
std::swap( a, b );
else if( !a->IsConnected() && b->IsConnected() )
std::swap( a, b );
if( !IsCopperLayer( layer ) )
{
r->Report( wxString::Format( _( "Active layer (%s) is not a copper layer. "
"No clearance defined." ),
m_frame->GetBoard()->GetLayerName( layer ) ) );
}
else if( !a->GetLayerSet().test( layer ) )
{
r->Report( wxString::Format( _( "%s not present on layer %s. No clearance defined." ),
a->GetSelectMenuText( r->GetUnits() ),
m_frame->GetBoard()->GetLayerName( layer ) ) );
}
else if( !b->GetLayerSet().test( layer ) )
{
r->Report( wxString::Format( _( "%s not present on layer %s. No clearance defined." ),
b->GetSelectMenuText( r->GetUnits() ),
m_frame->GetBoard()->GetLayerName( layer ) ) );
}
else if( !a->IsConnected() )
{
r->Report( _( "Items have no electrical connections. No clearance defined." ) );
}
else
{
r->Report( _( "<h7>Clearance resolution for:</h7>" ) );
r->Report( wxString::Format( _( "<ul><li>Layer %s</li><li>%s</li><li>%s</li></ul>" ),
m_frame->GetBoard()->GetLayerName( layer ),
a->GetSelectMenuText( r->GetUnits() ),
b->GetSelectMenuText( r->GetUnits() ) ) );
BOARD_CONNECTED_ITEM* ac = dynamic_cast<BOARD_CONNECTED_ITEM*>( a );
BOARD_CONNECTED_ITEM* bc = dynamic_cast<BOARD_CONNECTED_ITEM*>( b );
if( ac && bc && ac->GetNetCode() > 0 && ac->GetNetCode() == bc->GetNetCode() )
{
// Same nets....
if( ac->Type() == PCB_ZONE_AREA_T && bc->Type() == PCB_PAD_T )
{
reportZoneConnection( static_cast<ZONE_CONTAINER*>( ac ),
static_cast<D_PAD*>( bc ), r );
}
else
{
r->Report( _( "Items belong to the same net. Clearance is 0." ) );
}
}
else
{
// Different nets (or second unconnected)....
reportCopperClearance( layer, ac, b, r );
}
}
r->Flush();
m_inspectClearanceDialog->Show( true );
return 0;
}
int PCB_INSPECTION_TOOL::CrossProbePcbToSch( const TOOL_EVENT& aEvent )
{
// Don't get in an infinite loop PCB -> SCH -> PCB -> SCH -> ...
@ -537,6 +729,16 @@ void PCB_INSPECTION_TOOL::onListNetsDialogClosed( wxCommandEvent& event )
}
void PCB_INSPECTION_TOOL::onInspectClearanceDialogClosed( wxCommandEvent& event )
{
m_inspectClearanceDialog->Disconnect( wxEVT_CLOSE_WINDOW,
wxCommandEventHandler( PCB_INSPECTION_TOOL::onListNetsDialogClosed ), nullptr, this );
m_inspectClearanceDialog->Destroy();
m_inspectClearanceDialog.release();
}
int PCB_INSPECTION_TOOL::HideNet( const TOOL_EVENT& aEvent )
{
doHideNet( aEvent.Parameter<intptr_t>(), true );
@ -595,6 +797,7 @@ void PCB_INSPECTION_TOOL::setTransitions()
Go( &PCB_INSPECTION_TOOL::ListNets, PCB_ACTIONS::listNets.MakeEvent() );
Go( &PCB_INSPECTION_TOOL::ShowStatisticsDialog, PCB_ACTIONS::boardStatistics.MakeEvent() );
Go( &PCB_INSPECTION_TOOL::InspectClearance, PCB_ACTIONS::inspectClearance.MakeEvent() );
Go( &PCB_INSPECTION_TOOL::HighlightNet, PCB_ACTIONS::highlightNet.MakeEvent() );
Go( &PCB_INSPECTION_TOOL::HighlightNet, PCB_ACTIONS::highlightNetSelection.MakeEvent() );

View File

@ -26,6 +26,7 @@
#include <dialogs/dialog_board_statistics.h>
#include <dialogs/dialog_select_net_from_list.h>
#include <dialogs/dialog_HTML_reporter_base.h>
#include <pcb_edit_frame.h>
#include <tools/pcb_actions.h>
#include <tools/pcb_tool_base.h>
@ -89,6 +90,9 @@ public:
///> Show the ratsnest for a given net
int ShowNet( const TOOL_EVENT& aEvent );
///> Show the clearance resolution for two selected items
int InspectClearance( const TOOL_EVENT& aEvent );
private:
///> Event handler to recalculate dynamic ratsnest
void ratsnestTimer( wxTimerEvent& aEvent );
@ -103,7 +107,8 @@ private:
///> Bind handlers to corresponding TOOL_ACTIONs
void setTransitions() override;
void onListNetsDialogClosed( wxCommandEvent& event );
void onListNetsDialogClosed( wxCommandEvent& aEvent );
void onInspectClearanceDialogClosed( wxCommandEvent& aEvent );
private:
PCB_EDIT_FRAME* m_frame; // Pointer to the currently used edit frame.
@ -116,6 +121,7 @@ private:
std::unique_ptr<DIALOG_SELECT_NET_FROM_LIST> m_listNetsDialog;
DIALOG_SELECT_NET_FROM_LIST::SETTINGS m_listNetsDialogSettings;
std::unique_ptr<DIALOG_HTML_REPORTER> m_inspectClearanceDialog;
};
#endif //__BOARD_STATISTICS_TOOL_H