kicad-source/pcbnew/dialogs/dialog_drc.cpp
Ian McInerney 2fb6f19a84 Separate immediate and delayed action dispatch
Using a boolean argument just leads to a lot of trailing booleans in the
function calls and is not user friendly. Instead, introduce PostAction()
to send an action that runs after the coroutine (equivalent to passing
false or the default argument), and leave RunAction as the immediate
execution function.
2023-06-27 00:57:59 +01:00

1324 lines
41 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
* Copyright (C) 2009-2016 Dick Hollenbeck, dick@softplc.com
* Copyright (C) 2004-2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <confirm.h>
#include <dialog_drc.h>
#include <board_design_settings.h>
#include <kiface_base.h>
#include <macros.h>
#include <pad.h>
#include <drc/drc_item.h>
#include <connectivity/connectivity_data.h>
#include <connectivity/connectivity_algo.h>
#include <drawing_sheet/ds_proxy_view_item.h>
#include <pcb_edit_frame.h>
#include <pcbnew_settings.h>
#include <tool/tool_manager.h>
#include <tools/pcb_actions.h>
#include <wildcards_and_files_ext.h>
#include <pcb_marker.h>
#include <wx/filedlg.h>
#include <wx/wupdlock.h>
#include <widgets/appearance_controls.h>
#include <widgets/ui_common.h>
#include <widgets/progress_reporter_base.h>
#include <widgets/wx_html_report_box.h>
#include <dialogs/panel_setup_rules_base.h>
#include <tools/drc_tool.h>
#include <tools/zone_filler_tool.h>
#include <tools/board_inspection_tool.h>
#include <kiplatform/ui.h>
// wxWidgets spends *far* too long calcuating column widths (most of it, believe it or
// not, in repeatedly creating/destroying a wxDC to do the measurement in).
// Use default column widths instead.
static int DEFAULT_SINGLE_COL_WIDTH = 660;
static BOARD* g_lastDRCBoard = nullptr;
static bool g_lastDRCRun = false;
static bool g_lastFootprintTestsRun = false;
static std::vector<wxString> g_lastIgnored;
DIALOG_DRC::DIALOG_DRC( PCB_EDIT_FRAME* aEditorFrame, wxWindow* aParent ) :
DIALOG_DRC_BASE( aParent ),
PROGRESS_REPORTER_BASE( 1 ),
m_running( false ),
m_drcRun( false ),
m_footprintTestsRun( false ),
m_markersTreeModel( nullptr ),
m_unconnectedTreeModel( nullptr ),
m_fpWarningsTreeModel( nullptr ),
m_centerMarkerOnIdle( nullptr ),
m_severities( RPT_SEVERITY_ERROR | RPT_SEVERITY_WARNING )
{
SetName( DIALOG_DRC_WINDOW_NAME ); // Set a window name to be able to find it
m_frame = aEditorFrame;
m_currentBoard = m_frame->GetBoard();
m_messages->SetImmediateMode();
PCBNEW_SETTINGS* cfg = m_frame->GetPcbNewSettings();
m_severities = cfg->m_DrcDialog.severities;
m_markersProvider = std::make_shared<DRC_ITEMS_PROVIDER>( m_currentBoard,
MARKER_BASE::MARKER_DRC,
MARKER_BASE::MARKER_DRAWING_SHEET );
m_ratsnestProvider = std::make_shared<DRC_ITEMS_PROVIDER>( m_currentBoard,
MARKER_BASE::MARKER_RATSNEST );
m_fpWarningsProvider = std::make_shared<DRC_ITEMS_PROVIDER>( m_currentBoard,
MARKER_BASE::MARKER_PARITY );
m_markersTreeModel = new RC_TREE_MODEL( m_frame, m_markerDataView );
m_markerDataView->AssociateModel( m_markersTreeModel );
m_markersTreeModel->Update( m_markersProvider, m_severities );
m_unconnectedTreeModel = new RC_TREE_MODEL( m_frame, m_unconnectedDataView );
m_unconnectedDataView->AssociateModel( m_unconnectedTreeModel );
m_unconnectedTreeModel->Update( m_ratsnestProvider, m_severities );
m_fpWarningsTreeModel = new RC_TREE_MODEL( m_frame, m_footprintsDataView );
m_footprintsDataView->AssociateModel( m_fpWarningsTreeModel );
m_fpWarningsTreeModel->Update( m_fpWarningsProvider, m_severities );
m_ignoredList->InsertColumn( 0, wxEmptyString, wxLIST_FORMAT_LEFT, DEFAULT_SINGLE_COL_WIDTH );
if( m_currentBoard == g_lastDRCBoard )
{
m_drcRun = g_lastDRCRun;
m_footprintTestsRun = g_lastFootprintTestsRun;
for( const wxString& str : g_lastIgnored )
m_ignoredList->InsertItem( m_ignoredList->GetItemCount(), str );
}
m_Notebook->SetSelection( 0 );
if( Kiface().IsSingle() )
m_cbTestFootprints->Hide();
SetupStandardButtons( { { wxID_OK, _( "Run DRC" ) },
{ wxID_CANCEL, _( "Close" ) } } );
m_markersTitleTemplate = m_Notebook->GetPageText( 0 );
m_unconnectedTitleTemplate = m_Notebook->GetPageText( 1 );
m_footprintsTitleTemplate = m_Notebook->GetPageText( 2 );
m_ignoredTitleTemplate = m_Notebook->GetPageText( 3 );
m_cbRefillZones->SetValue( cfg->m_DrcDialog.refill_zones );
m_cbReportAllTrackErrors->SetValue( cfg->m_DrcDialog.test_all_track_errors );
if( !Kiface().IsSingle() )
m_cbTestFootprints->SetValue( cfg->m_DrcDialog.test_footprints );
Layout(); // adding the units above expanded Clearance text, now resize.
SetFocus();
syncCheckboxes();
finishDialogSettings();
}
DIALOG_DRC::~DIALOG_DRC()
{
m_frame->FocusOnItem( nullptr );
g_lastDRCBoard = m_currentBoard;
g_lastDRCRun = m_drcRun;
g_lastFootprintTestsRun = m_footprintTestsRun;
g_lastIgnored.clear();
for( int ii = 0; ii < m_ignoredList->GetItemCount(); ++ii )
g_lastIgnored.push_back( m_ignoredList->GetItemText( ii ) );
PCBNEW_SETTINGS* settings = m_frame->GetPcbNewSettings();
settings->m_DrcDialog.refill_zones = m_cbRefillZones->GetValue();
settings->m_DrcDialog.test_all_track_errors = m_cbReportAllTrackErrors->GetValue();
if( !Kiface().IsSingle() )
settings->m_DrcDialog.test_footprints = m_cbTestFootprints->GetValue();
settings->m_DrcDialog.severities = m_severities;
m_markersTreeModel->DecRef();
m_unconnectedTreeModel->DecRef();
m_fpWarningsTreeModel->DecRef();
}
void DIALOG_DRC::OnActivateDlg( wxActivateEvent& aEvent )
{
if( m_currentBoard != m_frame->GetBoard() )
{
// If m_currentBoard is not the current board, (for instance because a new board
// was loaded), close the dialog, because many pointers are now invalid in lists
SetReturnCode( wxID_CANCEL );
Close();
DRC_TOOL* drcTool = m_frame->GetToolManager()->GetTool<DRC_TOOL>();
drcTool->DestroyDRCDialog();
}
}
// PROGRESS_REPORTER calls
bool DIALOG_DRC::updateUI()
{
double cur = alg::clamp( 0.0, (double) m_progress.load() / m_maxProgress, 1.0 );
m_gauge->SetValue( KiROUND( cur * 1000.0 ) );
wxSafeYield( this );
return !m_cancelled;
}
void DIALOG_DRC::AdvancePhase( const wxString& aMessage )
{
PROGRESS_REPORTER_BASE::AdvancePhase( aMessage );
SetCurrentProgress( 0.0 );
m_messages->Report( aMessage );
}
// Don't globally define this; different facilities use different definitions of "ALL"
static int RPT_SEVERITY_ALL = RPT_SEVERITY_WARNING | RPT_SEVERITY_ERROR | RPT_SEVERITY_EXCLUSION;
void DIALOG_DRC::syncCheckboxes()
{
m_showAll->SetValue( m_severities == RPT_SEVERITY_ALL );
m_showErrors->SetValue( m_severities & RPT_SEVERITY_ERROR );
m_showWarnings->SetValue( m_severities & RPT_SEVERITY_WARNING );
m_showExclusions->SetValue( m_severities & RPT_SEVERITY_EXCLUSION );
}
void DIALOG_DRC::OnErrorLinkClicked( wxHtmlLinkEvent& event )
{
m_frame->ShowBoardSetupDialog( _( "Custom Rules" ) );
}
void DIALOG_DRC::OnRunDRCClick( wxCommandEvent& aEvent )
{
TOOL_MANAGER* toolMgr = m_frame->GetToolManager();
DRC_TOOL* drcTool = toolMgr->GetTool<DRC_TOOL>();
ZONE_FILLER_TOOL* zoneFillerTool = toolMgr->GetTool<ZONE_FILLER_TOOL>();
bool refillZones = m_cbRefillZones->GetValue();
bool reportAllTrackErrors = m_cbReportAllTrackErrors->GetValue();
bool testFootprints = m_cbTestFootprints->GetValue();
if( zoneFillerTool->IsBusy() )
{
wxBell();
return;
}
// This is not the time to have stale or buggy rules. Ensure they're up-to-date
// and that they at least parse.
try
{
drcTool->GetDRCEngine()->InitEngine( m_frame->GetDesignRulesPath() );
}
catch( PARSE_ERROR& )
{
m_runningResultsBook->ChangeSelection( 0 ); // Display the "Tests Running..." tab
m_DeleteCurrentMarkerButton->Enable( false );
m_DeleteAllMarkersButton->Enable( false );
m_saveReport->Enable( false );
m_messages->Clear();
m_messages->Report( _( "DRC incomplete: could not compile custom design rules." )
+ wxS( "&nbsp;&nbsp;" )
+ wxS( "<a href='$CUSTOM_RULES'>" ) + _( "Show design rules." ) + wxT( "</a>" ) );
m_messages->Flush();
Raise();
return;
}
m_footprintTestsRun = false;
m_cancelled = false;
m_frame->RecordDRCExclusions();
deleteAllMarkers( true );
std::vector<std::reference_wrapper<RC_ITEM>> violations = DRC_ITEM::GetItemsWithSeverities();
m_ignoredList->DeleteAllItems();
for( std::reference_wrapper<RC_ITEM>& item : violations )
{
if( bds().GetSeverity( item.get().GetErrorCode() ) == RPT_SEVERITY_IGNORE )
{
m_ignoredList->InsertItem( m_ignoredList->GetItemCount(),
wxT( "" ) + item.get().GetErrorText() );
}
}
m_ignoredList->SetColumnWidth( 0, m_ignoredList->GetParent()->GetClientSize().x - 20 );
Raise();
m_runningResultsBook->ChangeSelection( 0 ); // Display the "Tests Running..." tab
m_messages->Clear();
wxYield(); // Allow time slice to refresh Messages
m_running = true;
m_sdbSizerCancel->SetLabel( _( "Cancel" ) );
m_sdbSizerOK->Enable( false );
m_DeleteCurrentMarkerButton->Enable( false );
m_DeleteAllMarkersButton->Enable( false );
m_saveReport->Enable( false );
{
wxBusyCursor dummy;
drcTool->RunTests( this, refillZones, reportAllTrackErrors, testFootprints );
}
if( m_cancelled )
m_messages->Report( _( "-------- DRC cancelled by user.<br><br>" ) );
else
m_messages->Report( _( "Done.<br><br>" ) );
Raise();
wxYield(); // Allow time slice to refresh Messages
m_running = false;
m_sdbSizerCancel->SetLabel( _( "Close" ) );
m_sdbSizerOK->Enable( true );
m_DeleteCurrentMarkerButton->Enable( true );
m_DeleteAllMarkersButton->Enable( true );
m_saveReport->Enable( true );
if( !m_cancelled )
{
wxMilliSleep( 500 );
m_runningResultsBook->ChangeSelection( 1 );
#ifndef __WXGTK__
KIPLATFORM::UI::ForceFocus( m_markerDataView );
#endif
}
refreshEditor();
}
void DIALOG_DRC::UpdateData()
{
m_markersTreeModel->Update( m_markersProvider, m_severities );
m_unconnectedTreeModel->Update( m_ratsnestProvider, m_severities );
m_fpWarningsTreeModel->Update( m_fpWarningsProvider, m_severities );
updateDisplayedCounts();
}
void DIALOG_DRC::OnDRCItemSelected( wxDataViewEvent& aEvent )
{
BOARD* board = m_frame->GetBoard();
RC_TREE_NODE* node = RC_TREE_MODEL::ToNode( aEvent.GetItem() );
auto getActiveLayers =
[]( BOARD_ITEM* aItem ) -> LSET
{
if( aItem->Type() == PCB_PAD_T )
{
PAD* pad = static_cast<PAD*>( aItem );
LSET layers;
for( int layer : aItem->GetLayerSet().Seq() )
{
if( pad->FlashLayer( layer ) )
layers.set( layer );
}
return layers;
}
else
{
return aItem->GetLayerSet();
}
};
if( !node )
{
// list is being freed; don't do anything with null ptrs
aEvent.Skip();
return;
}
if( m_centerMarkerOnIdle )
{
// we already came from a cross-probe of the marker in the document; don't go
// around in circles
aEvent.Skip();
return;
}
std::shared_ptr<RC_ITEM> rc_item = node->m_RcItem;
if( rc_item->GetErrorCode() == DRCE_UNRESOLVED_VARIABLE
&& rc_item->GetParent()->GetMarkerType() == MARKER_BASE::MARKER_DRAWING_SHEET )
{
m_frame->FocusOnLocation( node->m_RcItem->GetParent()->GetPos() );
aEvent.Skip();
return;
}
const KIID& itemID = RC_TREE_MODEL::ToUUID( aEvent.GetItem() );
BOARD_ITEM* item = board->GetItem( itemID );
if( !item || item == DELETED_BOARD_ITEM::GetInstance() )
{
// nothing to highlight / focus on
aEvent.Skip();
return;
}
PCB_LAYER_ID principalLayer;
LSET violationLayers;
BOARD_ITEM* a = board->GetItem( rc_item->GetMainItemID() );
BOARD_ITEM* b = board->GetItem( rc_item->GetAuxItemID() );
BOARD_ITEM* c = board->GetItem( rc_item->GetAuxItem2ID() );
BOARD_ITEM* d = board->GetItem( rc_item->GetAuxItem3ID() );
if( rc_item->GetErrorCode() == DRCE_MALFORMED_COURTYARD )
{
if( a && ( a->GetFlags() & MALFORMED_B_COURTYARD ) > 0
&& ( a->GetFlags() & MALFORMED_F_COURTYARD ) == 0 )
{
principalLayer = B_CrtYd;
}
else
{
principalLayer = F_CrtYd;
}
}
else if (rc_item->GetErrorCode() == DRCE_INVALID_OUTLINE )
{
principalLayer = Edge_Cuts;
}
else
{
principalLayer = UNDEFINED_LAYER;
if( a || b || c || d )
violationLayers = LSET::AllLayersMask();
// Try to initialize principalLayer to a valid layer. Note that some markers have
// a layer set to UNDEFINED_LAYER, so we may need to keep looking.
for( BOARD_ITEM* it: { a, b, c, d } )
{
if( !it )
continue;
LSET layersList = getActiveLayers( it );
violationLayers &= layersList;
if( principalLayer <= UNDEFINED_LAYER && layersList.count() )
principalLayer = layersList.Seq().front();
}
}
if( violationLayers.count() )
principalLayer = violationLayers.Seq().front();
else if( !(principalLayer <= UNDEFINED_LAYER ) )
violationLayers.set( principalLayer );
WINDOW_THAWER thawer( m_frame );
if( principalLayer > UNDEFINED_LAYER && ( violationLayers & board->GetVisibleLayers() ) == 0 )
m_frame->GetAppearancePanel()->SetLayerVisible( principalLayer, true );
if( principalLayer > UNDEFINED_LAYER && board->GetVisibleLayers().test( principalLayer ) )
m_frame->SetActiveLayer( principalLayer );
if( rc_item->GetErrorCode() == DRCE_UNCONNECTED_ITEMS )
{
if( !m_frame->GetPcbNewSettings()->m_Display.m_ShowGlobalRatsnest )
m_frame->GetToolManager()->RunAction( PCB_ACTIONS::showRatsnest );
if( item->Type() == PCB_ZONE_T )
{
m_frame->GetBoard()->GetConnectivity()->RunOnUnconnectedEdges(
[&]( CN_EDGE& edge )
{
if( edge.GetSourceNode()->Parent() == a
&& edge.GetTargetNode()->Parent() == b )
{
if( item == a )
m_frame->FocusOnLocation( edge.GetSourcePos() );
else
m_frame->FocusOnLocation( edge.GetTargetPos() );
return false;
}
return true;
} );
}
else
{
m_frame->FocusOnItem( item, principalLayer );
}
}
else if( rc_item->GetErrorCode() == DRCE_DIFF_PAIR_UNCOUPLED_LENGTH_TOO_LONG )
{
BOARD_CONNECTED_ITEM* track = dynamic_cast<PCB_TRACK*>( item );
std::vector<BOARD_ITEM*> items;
if( track )
{
int net = track->GetNetCode();
wxASSERT( net > 0 ); // Without a net how can it be a diff-pair?
for( const KIID& id : rc_item->GetIDs() )
{
auto* candidate = dynamic_cast<BOARD_CONNECTED_ITEM*>( board->GetItem( id ) );
if( candidate && candidate->GetNetCode() == net )
items.push_back( candidate );
}
}
else
{
items.push_back( item );
}
m_frame->FocusOnItems( items, principalLayer );
}
else
{
m_frame->FocusOnItem( item, principalLayer );
}
aEvent.Skip();
}
void DIALOG_DRC::OnDRCItemDClick( wxDataViewEvent& aEvent )
{
if( aEvent.GetItem().IsOk() )
{
// turn control over to m_frame, hide this DIALOG_DRC window,
// no destruction so we can preserve listbox cursor
if( !IsModal() )
Show( false );
}
// Do not skip aEvent here: this is not useful, and Pcbnew crashes
// if skipped (at least on Windows)
}
void DIALOG_DRC::OnDRCItemRClick( wxDataViewEvent& aEvent )
{
RC_TREE_NODE* node = RC_TREE_MODEL::ToNode( aEvent.GetItem() );
if( !node )
return;
std::shared_ptr<RC_ITEM> rcItem = node->m_RcItem;
DRC_ITEM* drcItem = static_cast<DRC_ITEM*>( rcItem.get() );
std::shared_ptr<CONNECTIVITY_DATA> conn = m_currentBoard->GetConnectivity();
wxString listName;
wxMenu menu;
wxString msg;
switch( bds().m_DRCSeverities[ rcItem->GetErrorCode() ] )
{
case RPT_SEVERITY_ERROR: listName = _( "errors" ); break;
case RPT_SEVERITY_WARNING: listName = _( "warnings" ); break;
default: listName = _( "appropriate" ); break;
}
if( rcItem->GetParent()->IsExcluded() )
{
menu.Append( 1, _( "Remove exclusion for this violation" ),
wxString::Format( _( "It will be placed back in the %s list" ), listName ) );
if( drcItem->GetViolatingRule() && !drcItem->GetViolatingRule()->m_Implicit )
{
msg.Printf( _( "Remove all exclusions for violations of rule '%s'" ),
drcItem->GetViolatingRule()->m_Name );
menu.Append( 11, msg );
}
}
else
{
menu.Append( 2, _( "Exclude this violation" ),
wxString::Format( _( "It will be excluded from the %s list" ), listName ) );
if( drcItem->GetViolatingRule() && !drcItem->GetViolatingRule()->m_Implicit )
{
msg.Printf( _( "Exclude all violations of rule '%s'" ),
drcItem->GetViolatingRule()->m_Name );
menu.Append( 21, msg );
}
}
if( rcItem->GetErrorCode() == DRCE_CLEARANCE
|| rcItem->GetErrorCode() == DRCE_EDGE_CLEARANCE
|| rcItem->GetErrorCode() == DRCE_HOLE_CLEARANCE
|| rcItem->GetErrorCode() == DRCE_DRILLED_HOLES_TOO_CLOSE )
{
menu.Append( 3, _( "Run Inspect > Clearance Resolution" ) );
}
else if( rcItem->GetErrorCode() == DRCE_TEXT_HEIGHT
|| rcItem->GetErrorCode() == DRCE_TEXT_THICKNESS
|| rcItem->GetErrorCode() == DRCE_DIFF_PAIR_UNCOUPLED_LENGTH_TOO_LONG
|| rcItem->GetErrorCode() == DRCE_TRACK_WIDTH
|| rcItem->GetErrorCode() == DRCE_VIA_DIAMETER
|| rcItem->GetErrorCode() == DRCE_ANNULAR_WIDTH
|| rcItem->GetErrorCode() == DRCE_DRILL_OUT_OF_RANGE
|| rcItem->GetErrorCode() == DRCE_MICROVIA_DRILL_OUT_OF_RANGE
|| rcItem->GetErrorCode() == DRCE_CONNECTION_WIDTH
|| rcItem->GetErrorCode() == DRCE_ASSERTION_FAILURE )
{
menu.Append( 3, _( "Run Inspect > Constraints Resolution" ) );
}
else if( rcItem->GetErrorCode() == DRCE_LIB_FOOTPRINT_MISMATCH )
{
menu.Append( 3, _( "Run Inspect > Diff Footprint with Library" ) );
}
menu.AppendSeparator();
if( bds().m_DRCSeverities[ rcItem->GetErrorCode() ] == RPT_SEVERITY_WARNING )
{
msg.Printf( _( "Change severity to Error for all '%s' violations" ),
rcItem->GetErrorText(),
_( "Violation severities can also be edited in the Board Setup... dialog" ) );
menu.Append( 4, msg );
}
else
{
msg.Printf( _( "Change severity to Warning for all '%s' violations" ),
rcItem->GetErrorText(),
_( "Violation severities can also be edited in the Board Setup... dialog" ) );
menu.Append( 5, msg );
}
msg.Printf( _( "Ignore all '%s' violations" ),
rcItem->GetErrorText(),
_( "Violations will not be checked or reported" ) );
menu.Append( 6, msg );
menu.AppendSeparator();
menu.Append( 7, _( "Edit violation severities..." ), _( "Open the Board Setup... dialog" ) );
bool modified = false;
switch( GetPopupMenuSelectionFromUser( menu ) )
{
case 1:
{
PCB_MARKER* marker = dynamic_cast<PCB_MARKER*>( rcItem->GetParent() );
if( marker )
{
marker->SetExcluded( false );
if( rcItem->GetErrorCode() == DRCE_UNCONNECTED_ITEMS )
{
m_frame->GetBoard()->UpdateRatsnestExclusions();
m_frame->GetCanvas()->RedrawRatsnest();
}
else
{
m_frame->GetCanvas()->GetView()->Update( marker );
}
// Update view
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->ValueChanged( node );
modified = true;
}
break;
}
case 2:
{
PCB_MARKER* marker = dynamic_cast<PCB_MARKER*>( rcItem->GetParent() );
if( marker )
{
marker->SetExcluded( true );
if( rcItem->GetErrorCode() == DRCE_UNCONNECTED_ITEMS )
{
m_frame->GetBoard()->UpdateRatsnestExclusions();
m_frame->GetCanvas()->RedrawRatsnest();
}
else
{
m_frame->GetCanvas()->GetView()->Update( marker );
}
// Update view
if( m_severities & RPT_SEVERITY_EXCLUSION )
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->ValueChanged( node );
else
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->DeleteCurrentItem( false );
modified = true;
}
break;
}
case 11:
{
for( PCB_MARKER* marker : m_frame->GetBoard()->Markers() )
{
DRC_ITEM* candidateDrcItem = static_cast<DRC_ITEM*>( marker->GetRCItem().get() );
if( candidateDrcItem->GetViolatingRule() == drcItem->GetViolatingRule() )
marker->SetExcluded( false );
}
// Rebuild model and view
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->Update( m_markersProvider, m_severities );
modified = true;
break;
}
case 21:
{
for( PCB_MARKER* marker : m_frame->GetBoard()->Markers() )
{
DRC_ITEM* candidateDrcItem = static_cast<DRC_ITEM*>( marker->GetRCItem().get() );
if( candidateDrcItem->GetViolatingRule() == drcItem->GetViolatingRule() )
marker->SetExcluded( true );
}
// Rebuild model and view
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->Update( m_markersProvider, m_severities );
modified = true;
break;
}
case 3:
{
TOOL_MANAGER* toolMgr = m_frame->GetToolManager();
BOARD_INSPECTION_TOOL* inspectionTool = toolMgr->GetTool<BOARD_INSPECTION_TOOL>();
inspectionTool->InspectDRCError( node->m_RcItem );
break;
}
case 4:
bds().m_DRCSeverities[ rcItem->GetErrorCode() ] = RPT_SEVERITY_ERROR;
for( PCB_MARKER* marker : m_frame->GetBoard()->Markers() )
{
if( marker->GetRCItem()->GetErrorCode() == rcItem->GetErrorCode() )
m_frame->GetCanvas()->GetView()->Update( marker );
}
// Rebuild model and view
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->Update( m_markersProvider, m_severities );
modified = true;
break;
case 5:
bds().m_DRCSeverities[ rcItem->GetErrorCode() ] = RPT_SEVERITY_WARNING;
for( PCB_MARKER* marker : m_frame->GetBoard()->Markers() )
{
if( marker->GetRCItem()->GetErrorCode() == rcItem->GetErrorCode() )
m_frame->GetCanvas()->GetView()->Update( marker );
}
// Rebuild model and view
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->Update( m_markersProvider, m_severities );
modified = true;
break;
case 6:
{
bds().m_DRCSeverities[ rcItem->GetErrorCode() ] = RPT_SEVERITY_IGNORE;
m_ignoredList->InsertItem( m_ignoredList->GetItemCount(),
wxT( "" ) + rcItem->GetErrorText() );
std::vector<PCB_MARKER*>& markers = m_frame->GetBoard()->Markers();
for( unsigned i = 0; i < markers.size(); )
{
if( markers[i]->GetRCItem()->GetErrorCode() == rcItem->GetErrorCode() )
{
m_frame->GetCanvas()->GetView()->Remove( markers.at( i ) );
markers.erase( markers.begin() + i );
}
else
{
++i;
}
}
if( rcItem->GetErrorCode() == DRCE_UNCONNECTED_ITEMS )
m_frame->GetCanvas()->RedrawRatsnest();
// Rebuild model and view
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->Update( m_markersProvider, m_severities );
modified = true;
break;
}
case 7:
m_frame->ShowBoardSetupDialog( _( "Violation Severity" ) );
break;
}
if( modified )
{
updateDisplayedCounts();
refreshEditor();
m_frame->OnModify();
}
}
void DIALOG_DRC::OnEditViolationSeverities( wxHyperlinkEvent& aEvent )
{
m_frame->ShowBoardSetupDialog( _( "Violation Severity" ) );
}
void DIALOG_DRC::OnSeverity( wxCommandEvent& aEvent )
{
int flag = 0;
if( aEvent.GetEventObject() == m_showAll )
flag = RPT_SEVERITY_ALL;
else if( aEvent.GetEventObject() == m_showErrors )
flag = RPT_SEVERITY_ERROR;
else if( aEvent.GetEventObject() == m_showWarnings )
flag = RPT_SEVERITY_WARNING;
else if( aEvent.GetEventObject() == m_showExclusions )
flag = RPT_SEVERITY_EXCLUSION;
if( aEvent.IsChecked() )
m_severities |= flag;
else if( aEvent.GetEventObject() == m_showAll )
m_severities = RPT_SEVERITY_ERROR;
else
m_severities &= ~flag;
syncCheckboxes();
UpdateData();
}
void DIALOG_DRC::OnSaveReport( wxCommandEvent& aEvent )
{
wxFileName fn( "DRC." + ReportFileExtension );
wxFileDialog dlg( this, _( "Save Report to File" ), Prj().GetProjectPath(), fn.GetFullName(),
ReportFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
if( dlg.ShowModal() != wxID_OK )
return;
fn = dlg.GetPath();
if( fn.GetExt().IsEmpty() )
fn.SetExt( ReportFileExtension );
if( !fn.IsAbsolute() )
{
wxString prj_path = Prj().GetProjectPath();
fn.MakeAbsolute( prj_path );
}
if( writeReport( fn.GetFullPath() ) )
{
m_messages->Report( wxString::Format( _( "Report file '%s' created<br>" ),
fn.GetFullPath() ) );
}
else
{
DisplayError( this, wxString::Format( _( "Failed to create file '%s'." ),
fn.GetFullPath() ) );
}
}
void DIALOG_DRC::OnClose( wxCloseEvent& aEvent )
{
if( m_running )
aEvent.Veto();
wxCommandEvent dummy;
OnCancelClick( dummy );
}
void DIALOG_DRC::OnCancelClick( wxCommandEvent& aEvent )
{
if( m_running )
{
m_cancelled = true;
return;
}
m_frame->FocusOnItem( nullptr );
SetReturnCode( wxID_CANCEL );
// The dialog can be modal or not modal.
// Leave the DRC caller destroy (or not) the dialog
DRC_TOOL* drcTool = m_frame->GetToolManager()->GetTool<DRC_TOOL>();
drcTool->DestroyDRCDialog();
}
void DIALOG_DRC::OnChangingNotebookPage( wxNotebookEvent& aEvent )
{
// Shouldn't be necessary, but is on at least OSX
if( aEvent.GetSelection() >= 0 )
m_Notebook->ChangeSelection( (unsigned) aEvent.GetSelection() );
m_markerDataView->UnselectAll();
m_unconnectedDataView->UnselectAll();
m_footprintsDataView->UnselectAll();
}
void DIALOG_DRC::refreshEditor()
{
WINDOW_THAWER thawer( m_frame );
m_frame->GetCanvas()->Refresh();
}
void DIALOG_DRC::PrevMarker()
{
if( m_Notebook->IsShown() )
{
switch( m_Notebook->GetSelection() )
{
case 0: m_markersTreeModel->PrevMarker(); break;
case 1: m_unconnectedTreeModel->PrevMarker(); break;
case 2: m_fpWarningsTreeModel->PrevMarker(); break;
case 3: break;
}
}
}
void DIALOG_DRC::NextMarker()
{
if( m_Notebook->IsShown() )
{
switch( m_Notebook->GetSelection() )
{
case 0: m_markersTreeModel->NextMarker(); break;
case 1: m_unconnectedTreeModel->NextMarker(); break;
case 2: m_fpWarningsTreeModel->NextMarker(); break;
case 3: break;
}
}
}
void DIALOG_DRC::SelectMarker( const PCB_MARKER* aMarker )
{
if( m_Notebook->IsShown() )
{
m_Notebook->SetSelection( 0 );
m_markersTreeModel->SelectMarker( aMarker );
// wxWidgets on some platforms fails to correctly ensure that a selected item is
// visible, so we have to do it in a separate idle event.
m_centerMarkerOnIdle = aMarker;
Bind( wxEVT_IDLE, &DIALOG_DRC::centerMarkerIdleHandler, this );
}
}
void DIALOG_DRC::centerMarkerIdleHandler( wxIdleEvent& aEvent )
{
m_markersTreeModel->CenterMarker( m_centerMarkerOnIdle );
m_centerMarkerOnIdle = nullptr;
Unbind( wxEVT_IDLE, &DIALOG_DRC::centerMarkerIdleHandler, this );
}
void DIALOG_DRC::ExcludeMarker()
{
if( !m_Notebook->IsShown() || m_Notebook->GetSelection() != 0 )
return;
RC_TREE_NODE* node = RC_TREE_MODEL::ToNode( m_markerDataView->GetCurrentItem() );
PCB_MARKER* marker = dynamic_cast<PCB_MARKER*>( node->m_RcItem->GetParent() );
if( marker && marker->GetSeverity() != RPT_SEVERITY_EXCLUSION )
{
marker->SetExcluded( true );
m_frame->GetCanvas()->GetView()->Update( marker );
// Update view
if( m_severities & RPT_SEVERITY_EXCLUSION )
m_markersTreeModel->ValueChanged( node );
else
m_markersTreeModel->DeleteCurrentItem( false );
updateDisplayedCounts();
refreshEditor();
m_frame->OnModify();
}
}
void DIALOG_DRC::deleteAllMarkers( bool aIncludeExclusions )
{
// Clear current selection list to avoid selection of deleted items
m_frame->GetToolManager()->RunAction( PCB_ACTIONS::selectionClear );
m_markersTreeModel->DeleteItems( false, aIncludeExclusions, false );
m_unconnectedTreeModel->DeleteItems( false, aIncludeExclusions, false );
m_fpWarningsTreeModel->DeleteItems( false, aIncludeExclusions, false );
m_frame->GetBoard()->DeleteMARKERs( true, aIncludeExclusions );
}
bool DIALOG_DRC::writeReport( const wxString& aFullFileName )
{
FILE* fp = wxFopen( aFullFileName, wxT( "w" ) );
if( fp == nullptr )
return false;
std::map<KIID, EDA_ITEM*> itemMap;
m_frame->GetBoard()->FillItemMap( itemMap );
BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
UNITS_PROVIDER unitsProvider( pcbIUScale, GetUserUnits() );
int count;
fprintf( fp, "** Drc report for %s **\n", TO_UTF8( m_frame->GetBoard()->GetFileName() ) );
wxDateTime now = wxDateTime::Now();
fprintf( fp, "** Created on %s **\n", TO_UTF8( now.Format( wxT( "%F %T" ) ) ) );
count = m_markersProvider->GetCount();
fprintf( fp, "\n** Found %d DRC violations **\n", count );
for( int i = 0; i < count; ++i )
{
const std::shared_ptr<RC_ITEM>& item = m_markersProvider->GetItem( i );
SEVERITY severity = item->GetParent()->GetSeverity();
if( severity == RPT_SEVERITY_EXCLUSION )
severity = bds.GetSeverity( item->GetErrorCode() );
fprintf( fp, "%s", TO_UTF8( item->ShowReport( &unitsProvider, severity, itemMap ) ) );
}
count = m_ratsnestProvider->GetCount();
fprintf( fp, "\n** Found %d unconnected pads **\n", count );
for( int i = 0; i < count; ++i )
{
const std::shared_ptr<RC_ITEM>& item = m_ratsnestProvider->GetItem( i );
SEVERITY severity = bds.GetSeverity( item->GetErrorCode() );
fprintf( fp, "%s", TO_UTF8( item->ShowReport( &unitsProvider, severity, itemMap ) ) );
}
count = m_fpWarningsProvider->GetCount();
fprintf( fp, "\n** Found %d Footprint errors **\n", count );
for( int i = 0; i < count; ++i )
{
const std::shared_ptr<RC_ITEM>& item = m_fpWarningsProvider->GetItem( i );
SEVERITY severity = bds.GetSeverity( item->GetErrorCode() );
fprintf( fp, "%s", TO_UTF8( item->ShowReport( &unitsProvider, severity, itemMap ) ) );
}
fprintf( fp, "\n** End of Report **\n" );
fclose( fp );
return true;
}
void DIALOG_DRC::OnDeleteOneClick( wxCommandEvent& aEvent )
{
if( m_Notebook->GetSelection() == 0 )
{
// Clear the selection. It may be the selected DRC marker.
m_frame->GetToolManager()->RunAction( PCB_ACTIONS::selectionClear );
m_markersTreeModel->DeleteCurrentItem( true );
// redraw the pcb
refreshEditor();
}
else if( m_Notebook->GetSelection() == 1 )
{
m_unconnectedTreeModel->DeleteCurrentItem( true );
}
else if( m_Notebook->GetSelection() == 2 )
{
m_fpWarningsTreeModel->DeleteCurrentItem( true );
}
updateDisplayedCounts();
}
void DIALOG_DRC::OnDeleteAllClick( wxCommandEvent& aEvent )
{
static bool s_includeExclusions = false;
int numExcluded = 0;
if( m_markersProvider )
numExcluded += m_markersProvider->GetCount( RPT_SEVERITY_EXCLUSION );
if( m_ratsnestProvider )
numExcluded += m_ratsnestProvider->GetCount( RPT_SEVERITY_EXCLUSION );
if( m_fpWarningsProvider )
numExcluded += m_fpWarningsProvider->GetCount( RPT_SEVERITY_EXCLUSION );
if( numExcluded > 0 )
{
wxRichMessageDialog dlg( this, _( "Do you wish to delete excluded markers as well?" ),
_( "Delete All Markers" ),
wxOK | wxCANCEL | wxCENTER | wxICON_QUESTION );
dlg.ShowCheckBox( _( "Delete exclusions" ), s_includeExclusions );
int ret = dlg.ShowModal();
if( ret == wxID_CANCEL )
return;
else
s_includeExclusions = dlg.IsCheckBoxChecked();
}
deleteAllMarkers( s_includeExclusions );
refreshEditor();
updateDisplayedCounts();
}
void DIALOG_DRC::updateDisplayedCounts()
{
BOARD_DESIGN_SETTINGS& bds = m_frame->GetDesignSettings();
DRC_TOOL* drcTool = m_frame->GetToolManager()->GetTool<DRC_TOOL>();
DRC_ENGINE* drcEngine = drcTool->GetDRCEngine().get();
// Collect counts:
int numMarkers = 0;
int numUnconnected = 0;
int numFootprints = 0;
int numErrors = 0;
int numWarnings = 0;
int numExcluded = 0;
if( m_markersProvider )
{
numMarkers += m_markersProvider->GetCount();
numErrors += m_markersProvider->GetCount( RPT_SEVERITY_ERROR );
numWarnings += m_markersProvider->GetCount( RPT_SEVERITY_WARNING );
numExcluded += m_markersProvider->GetCount( RPT_SEVERITY_EXCLUSION );
}
if( m_ratsnestProvider )
{
numUnconnected += m_ratsnestProvider->GetCount();
numErrors += m_ratsnestProvider->GetCount( RPT_SEVERITY_ERROR );
numWarnings += m_ratsnestProvider->GetCount( RPT_SEVERITY_WARNING );
numExcluded += m_ratsnestProvider->GetCount( RPT_SEVERITY_EXCLUSION );
}
if( m_footprintTestsRun && m_fpWarningsProvider )
{
numFootprints += m_fpWarningsProvider->GetCount();
numErrors += m_fpWarningsProvider->GetCount( RPT_SEVERITY_ERROR );
numWarnings += m_fpWarningsProvider->GetCount( RPT_SEVERITY_WARNING );
numExcluded += m_fpWarningsProvider->GetCount( RPT_SEVERITY_EXCLUSION );
}
bool showErrors = m_showErrors->GetValue();
bool showWarnings = m_showWarnings->GetValue();
bool errorsOverflowed = false;
bool warningsOverflowed = false;
bool markersOverflowed = false;
bool unconnectedOverflowed = false;
bool footprintsOverflowed = false;
for( int ii = DRCE_FIRST; ii < DRCE_LAST; ++ii )
{
if( drcEngine->IsErrorLimitExceeded( ii ) )
{
if( bds.GetSeverity( ii ) == RPT_SEVERITY_ERROR )
errorsOverflowed = true;
else if( bds.GetSeverity( ii ) == RPT_SEVERITY_WARNING )
warningsOverflowed = true;
if( ii == DRCE_UNCONNECTED_ITEMS )
{
if( showWarnings && bds.GetSeverity( ii ) == RPT_SEVERITY_WARNING )
unconnectedOverflowed = true;
else if( showErrors && bds.GetSeverity( ii ) == RPT_SEVERITY_ERROR )
unconnectedOverflowed = true;
}
else if( ii == DRCE_MISSING_FOOTPRINT
|| ii == DRCE_DUPLICATE_FOOTPRINT
|| ii == DRCE_EXTRA_FOOTPRINT
|| ii == DRCE_NET_CONFLICT )
{
if( showWarnings && bds.GetSeverity( ii ) == RPT_SEVERITY_WARNING )
footprintsOverflowed = true;
else if( showErrors && bds.GetSeverity( ii ) == RPT_SEVERITY_ERROR )
footprintsOverflowed = true;
}
else
{
if( showWarnings && bds.GetSeverity( ii ) == RPT_SEVERITY_WARNING )
markersOverflowed = true;
else if( showErrors && bds.GetSeverity( ii ) == RPT_SEVERITY_ERROR )
markersOverflowed = true;
}
}
}
wxString msg;
wxString num;
// Update tab headers:
if( m_drcRun )
{
num.Printf( markersOverflowed ? wxT( "%d+" ) : wxT( "%d" ), numMarkers );
msg.Printf( m_markersTitleTemplate, num );
}
else
{
msg = m_markersTitleTemplate;
msg.Replace( wxT( "(%s)" ), wxEmptyString );
}
m_Notebook->SetPageText( 0, msg );
if( m_drcRun )
{
num.Printf( unconnectedOverflowed ? wxT( "%d+" ) : wxT( "%d" ), numUnconnected );
msg.sprintf( m_unconnectedTitleTemplate, num );
}
else
{
msg = m_unconnectedTitleTemplate;
msg.Replace( wxT( "(%s)" ), wxEmptyString );
}
m_Notebook->SetPageText( 1, msg );
if( m_footprintTestsRun )
{
num.Printf( footprintsOverflowed ? wxT( "%d+" ) : wxT( "%d" ), numFootprints );
msg.sprintf( m_footprintsTitleTemplate, num );
}
else if( m_drcRun )
{
msg = m_footprintsTitleTemplate;
msg.Replace( wxT( "%s" ), _( "not run" ) );
}
else
{
msg = m_footprintsTitleTemplate;
msg.Replace( wxT( "(%s)" ), wxEmptyString );
}
m_Notebook->SetPageText( 2, msg );
if( m_drcRun )
{
num.Printf( wxT( "%d" ), m_ignoredList->GetItemCount() );
msg.sprintf( m_ignoredTitleTemplate, num );
}
else
{
msg = m_ignoredTitleTemplate;
msg.Replace( wxT( "(%s)" ), wxEmptyString );
}
m_Notebook->SetPageText( 3, msg );
// Update badges:
if( !m_drcRun && numErrors == 0 )
numErrors = -1;
if( !m_drcRun && numWarnings == 0 )
numWarnings = -1;
m_errorsBadge->SetMaximumNumber( numErrors );
m_errorsBadge->UpdateNumber( errorsOverflowed ? numErrors + 1 : numErrors,
RPT_SEVERITY_ERROR );
m_warningsBadge->SetMaximumNumber( numWarnings );
m_warningsBadge->UpdateNumber( warningsOverflowed ? numWarnings + 1 : numWarnings,
RPT_SEVERITY_WARNING );
m_exclusionsBadge->SetMaximumNumber( numExcluded );
m_exclusionsBadge->UpdateNumber( numExcluded, RPT_SEVERITY_EXCLUSION );
}