/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2015 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2012 Wayne Stambaugh * Copyright (C) 1992-2019 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern int DiagErc[ELECTRICAL_PINTYPES_TOTAL][ELECTRICAL_PINTYPES_TOTAL]; extern int DefaultDiagErc[ELECTRICAL_PINTYPES_TOTAL][ELECTRICAL_PINTYPES_TOTAL]; bool DIALOG_ERC::m_diagErcTableInit = false; // saved only for the current session // Control identifiers for events #define ID_MATRIX_0 1800 BEGIN_EVENT_TABLE( DIALOG_ERC, DIALOG_ERC_BASE ) EVT_COMMAND_RANGE( ID_MATRIX_0, ID_MATRIX_0 + ( ELECTRICAL_PINTYPES_TOTAL * ELECTRICAL_PINTYPES_TOTAL ) - 1, wxEVT_COMMAND_BUTTON_CLICKED, DIALOG_ERC::ChangeErrorLevel ) END_EVENT_TABLE() DIALOG_ERC::DIALOG_ERC( SCH_EDIT_FRAME* parent ) : DIALOG_ERC_BASE( parent, ID_DIALOG_ERC ), // parent looks for this ID explicitly m_buttonList(), m_initialized( false ), m_settings() { m_parent = parent; m_lastMarkerFound = nullptr; wxFont infoFont = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT ); infoFont.SetSymbolicSize( wxFONTSIZE_SMALL ); m_textMarkers->SetFont( infoFont ); m_titleMessages->SetFont( infoFont ); Init(); // We use a sdbSizer to get platform-dependent ordering of the action buttons, but // that requires us to correct the button labels here. m_sdbSizer1OK->SetLabel( _( "Run" ) ); m_sdbSizer1Cancel->SetLabel( _( "Close" ) ); m_sdbSizer1->Layout(); m_sdbSizer1OK->SetDefault(); // Now all widgets have the size fixed, call FinishDialogSettings FinishDialogSettings(); } DIALOG_ERC::~DIALOG_ERC() { transferControlsToSettings(); if( m_settings != m_parent->GetErcSettings() ) { m_parent->UpdateErcSettings( m_settings ); m_parent->SaveProjectSettings( false ); } } void DIALOG_ERC::Init() { m_initialized = false; for( auto& buttonRow : m_buttonList ) { for( auto& button : buttonRow ) button = NULL; } m_settings = m_parent->GetErcSettings(); transferSettingsToControls(); SCH_SCREENS screens; updateMarkerCounts( &screens ); DisplayERC_MarkersList(); // Init Panel Matrix ReBuildMatrixPanel(); } void DIALOG_ERC::OnUpdateUI( wxUpdateUIEvent& event ) { m_buttondelmarkers->Show( m_NoteBook->GetSelection() == 0 ); m_ResetOptButton->Show( m_NoteBook->GetSelection() == 1 ); m_buttonsSizer->Layout(); } void DIALOG_ERC::transferSettingsToControls() { m_WriteResultOpt->SetValue( m_settings.write_erc_file ); m_cbTestSimilarLabels->SetValue( m_settings.check_similar_labels ); m_cbTestUniqueGlbLabels->SetValue( m_settings.check_unique_global_labels ); m_cbCheckBusDriverConflicts->SetValue( m_settings.check_bus_driver_conflicts ); m_cbCheckBusEntries->SetValue( m_settings.check_bus_entry_conflicts ); m_cbCheckBusToBusConflicts->SetValue( m_settings.check_bus_to_bus_conflicts ); m_cbCheckBusToNetConflicts->SetValue( m_settings.check_bus_to_net_conflicts ); } void DIALOG_ERC::transferControlsToSettings() { m_settings.write_erc_file = m_WriteResultOpt->GetValue(); m_settings.check_similar_labels = m_cbTestSimilarLabels->GetValue(); m_settings.check_unique_global_labels = m_cbTestUniqueGlbLabels->GetValue(); m_settings.check_bus_driver_conflicts = m_cbCheckBusDriverConflicts->GetValue(); m_settings.check_bus_entry_conflicts = m_cbCheckBusEntries->GetValue(); m_settings.check_bus_to_bus_conflicts = m_cbCheckBusToBusConflicts->GetValue(); m_settings.check_bus_to_net_conflicts = m_cbCheckBusToNetConflicts->GetValue(); } void DIALOG_ERC::updateMarkerCounts( SCH_SCREENS *screens ) { int markers = screens->GetMarkerCount( MARKER_BASE::MARKER_ERC, MARKER_BASE::MARKER_SEVERITY_UNSPEC ); int warnings = screens->GetMarkerCount( MARKER_BASE::MARKER_ERC, MARKER_BASE::MARKER_SEVERITY_WARNING ); int errors = screens->GetMarkerCount( MARKER_BASE::MARKER_ERC, MARKER_BASE::MARKER_SEVERITY_ERROR ); wxString num; num.Printf( wxT( "%d" ), markers ); m_TotalErrCount->SetValue( num ); num.Printf( wxT( "%d" ), errors ); m_LastErrCount->SetValue( num ); num.Printf( wxT( "%d" ), warnings ); m_LastWarningCount->SetValue( num ); } /* Delete the old ERC markers, over the whole hierarchy */ void DIALOG_ERC::OnEraseDrcMarkersClick( wxCommandEvent& event ) { SCH_SCREENS ScreenList; ScreenList.DeleteAllMarkers( MARKER_BASE::MARKER_ERC ); updateMarkerCounts( &ScreenList ); m_MarkersList->ClearList(); m_parent->GetCanvas()->Refresh(); } // This is a modeless dialog so we have to handle these ourselves. void DIALOG_ERC::OnButtonCloseClick( wxCommandEvent& event ) { Close(); } void DIALOG_ERC::OnCloseErcDialog( wxCloseEvent& event ) { Destroy(); } void DIALOG_ERC::OnResetMatrixClick( wxCommandEvent& event ) { ResetDefaultERCDiag( event ); } void DIALOG_ERC::OnErcCmpClick( wxCommandEvent& event ) { wxBusyCursor busy; m_MarkersList->ClearList(); m_MessagesList->Clear(); wxSafeYield(); // m_MarkersList must be redraw WX_TEXT_CTRL_REPORTER reporter( m_MessagesList ); TestErc( reporter ); } void DIALOG_ERC::RedrawDrawPanel() { WINDOW_THAWER thawer( m_parent ); m_parent->GetCanvas()->Refresh(); } void DIALOG_ERC::OnLeftClickMarkersList( wxHtmlLinkEvent& event ) { wxString link = event.GetLinkInfo().GetHref(); m_lastMarkerFound = nullptr; long index; if( !link.ToLong( &index ) ) return; const SCH_MARKER* marker = m_MarkersList->GetItem( index ); if( !marker ) return; // Search for the selected marker unsigned i; SCH_SHEET_LIST sheetList( g_RootSheet ); bool found = false; for( i = 0; i < sheetList.size(); i++ ) { for( auto aItem : sheetList[i].LastScreen()->Items().OfType( SCH_MARKER_T ) ) { if( static_cast( aItem ) == marker ) { found = true; break; } } if( found ) break; } if( !found ) // Error { wxMessageBox( _( "Marker not found" ) ); // The marker was deleted, so rebuild marker list DisplayERC_MarkersList(); return; } if( sheetList[i] != m_parent->GetCurrentSheet() ) { m_parent->GetToolManager()->RunAction( ACTIONS::cancelInteractive, true ); m_parent->GetToolManager()->RunAction( EE_ACTIONS::clearSelection, true ); m_parent->SetCurrentSheet( sheetList[i] ); m_parent->DisplayCurrentSheet(); sheetList[i].LastScreen()->SetZoom( m_parent->GetScreen()->GetZoom() ); m_parent->RedrawScreen( (wxPoint) m_parent->GetScreen()->m_ScrollCenter, false ); } m_lastMarkerFound = marker; m_parent->FocusOnLocation( marker->m_Pos ); RedrawDrawPanel(); } void DIALOG_ERC::OnLeftDblClickMarkersList( wxMouseEvent& event ) { // Remember: OnLeftClickMarkersList was called just before and therefore m_lastMarkerFound // was initialized (NULL if not found). if( m_lastMarkerFound ) { m_parent->FocusOnLocation( m_lastMarkerFound->m_Pos ); RedrawDrawPanel(); } Close(); } void DIALOG_ERC::ReBuildMatrixPanel() { // Try to know the size of bitmap button used in drc matrix wxBitmapButton * dummy = new wxBitmapButton( m_matrixPanel, wxID_ANY, KiBitmap( ercerr_xpm ) ); wxSize bitmap_size = dummy->GetSize(); delete dummy; if( !m_diagErcTableInit ) { memcpy( DiagErc, DefaultDiagErc, sizeof(DefaultDiagErc) ); m_diagErcTableInit = true; } wxPoint pos; // Get the current text size:use a dummy text. wxStaticText* text = new wxStaticText( m_matrixPanel, -1, wxT( "W" ), pos ); int text_height = text->GetRect().GetHeight(); bitmap_size.y = std::max( bitmap_size.y, text_height ); delete text; // compute the Y pos interval: pos.y = text_height; if( !m_initialized ) { std::vector labels; // Print row labels for( int ii = 0; ii < ELECTRICAL_PINTYPES_TOTAL; ii++ ) { int y = pos.y + (ii * bitmap_size.y); text = new wxStaticText( m_matrixPanel, -1, CommentERC_H[ii], wxPoint( 5, y + ( bitmap_size.y / 2) - (text_height / 2) ) ); labels.push_back( text ); int x = text->GetRect().GetRight(); pos.x = std::max( pos.x, x ); } // Right-align for( int ii = 0; ii < ELECTRICAL_PINTYPES_TOTAL; ii++ ) { wxPoint labelPos = labels[ ii ]->GetPosition(); labelPos.x = pos.x - labels[ ii ]->GetRect().GetWidth(); labels[ ii ]->SetPosition( labelPos ); } pos.x += 5; } else pos = m_buttonList[0][0]->GetPosition(); for( int ii = 0; ii < ELECTRICAL_PINTYPES_TOTAL; ii++ ) { int y = pos.y + (ii * bitmap_size.y); for( int jj = 0; jj <= ii; jj++ ) { // Add column labels (only once) int diag = DiagErc[ii][jj]; int x = pos.x + (jj * bitmap_size.x); if( (ii == jj) && !m_initialized ) { wxPoint txtpos; txtpos.x = x + (bitmap_size.x / 2); txtpos.y = y - text_height; text = new wxStaticText( m_matrixPanel, -1, CommentERC_V[ii], txtpos ); } int event_id = ID_MATRIX_0 + ii + ( jj * ELECTRICAL_PINTYPES_TOTAL ); BITMAP_DEF bitmap_butt = erc_green_xpm; delete m_buttonList[ii][jj]; m_buttonList[ii][jj] = new wxBitmapButton( m_matrixPanel, event_id, KiBitmap( bitmap_butt ), wxPoint( x, y ) ); setDRCMatrixButtonState( m_buttonList[ii][jj], diag ); } } m_initialized = true; } void DIALOG_ERC::setDRCMatrixButtonState( wxBitmapButton *aButton, int aState ) { BITMAP_DEF bitmap_butt = nullptr; wxString tooltip; switch( aState ) { case OK: bitmap_butt = erc_green_xpm; tooltip = _( "No error or warning" ); break; case WAR: bitmap_butt = ercwarn_xpm; tooltip = _( "Generate warning" ); break; case ERR: bitmap_butt = ercerr_xpm; tooltip = _( "Generate error" ); break; default: break; } if( bitmap_butt ) { aButton->SetBitmap( KiBitmap( bitmap_butt ) ); aButton->SetToolTip( tooltip ); } } void DIALOG_ERC::DisplayERC_MarkersList() { SCH_SHEET_LIST sheetList( g_RootSheet); m_MarkersList->ClearList(); for( unsigned i = 0; i < sheetList.size(); i++ ) { for( auto aItem : sheetList[i].LastScreen()->Items().OfType( SCH_MARKER_T ) ) { SCH_MARKER* marker = static_cast( aItem ); if( marker->GetMarkerType() == MARKER_BASE::MARKER_ERC ) m_MarkersList->AppendToList( marker ); } } m_MarkersList->DisplayList( GetUserUnits() ); } void DIALOG_ERC::ResetDefaultERCDiag( wxCommandEvent& event ) { memcpy( DiagErc, DefaultDiagErc, sizeof( DiagErc ) ); ReBuildMatrixPanel(); m_settings.LoadDefaults(); transferSettingsToControls(); } void DIALOG_ERC::ChangeErrorLevel( wxCommandEvent& event ) { int id = event.GetId(); int ii = id - ID_MATRIX_0; int x = ii / ELECTRICAL_PINTYPES_TOTAL; int y = ii % ELECTRICAL_PINTYPES_TOTAL; wxBitmapButton* butt = (wxBitmapButton*) event.GetEventObject(); int level = ( DiagErc[y][x] + 1 ) % 3; setDRCMatrixButtonState( butt, level ); DiagErc[y][x] = DiagErc[x][y] = level; } void DIALOG_ERC::TestErc( REPORTER& aReporter ) { wxFileName fn; transferControlsToSettings(); // Build the whole sheet list in hierarchy (sheet, not screen) SCH_SHEET_LIST sheets( g_RootSheet ); sheets.AnnotatePowerSymbols(); if( m_parent->CheckAnnotate( aReporter, false ) ) { if( aReporter.HasMessage() ) aReporter.ReportTail( _( "Annotation required!" ), REPORTER::RPT_ERROR ); return; } SCH_SCREENS screens; // Erase all previous DRC markers. screens.DeleteAllMarkers( MARKER_BASE::MARKER_ERC ); // Test duplicate sheet names inside a given sheet. While one can have multiple references // to the same file, each must have a unique name. TestDuplicateSheetNames( true ); TestConflictingBusAliases(); // The connection graph has a whole set of ERC checks it can run m_parent->RecalculateConnections( NO_CLEANUP ); g_ConnectionGraph->RunERC( m_settings ); // Test is all units of each multiunit component have the same footprint assigned. TestMultiunitFootprints( sheets ); std::unique_ptr objectsConnectedList( m_parent->BuildNetListBase() ); // Reset the connection type indicator objectsConnectedList->ResetConnectionsType(); unsigned lastItemIdx = 0; unsigned nextItemIdx = 0; int MinConn = NOC; // Check that a pin appears in only one net. This check is necessary because multi-unit // components that have shared pins could be wired to different nets. std::unordered_map pin_to_net_map; // The netlist generated by SCH_EDIT_FRAME::BuildNetListBase is sorted by net number, which // means we can group netlist items into ranges that live in the same net. The range from // nextItem to the current item (exclusive) needs to be checked against the current item. // The lastItem variable is used as a helper to pass the last item's number from one loop // iteration to the next, which simplifies the initial pass. for( unsigned itemIdx = 0; itemIdx < objectsConnectedList->size(); itemIdx++ ) { auto item = objectsConnectedList->GetItem( itemIdx ); auto lastItem = objectsConnectedList->GetItem( lastItemIdx ); auto lastNet = lastItem->GetNet(); auto net = item->GetNet(); wxASSERT_MSG( lastNet <= net, wxT( "Netlist not correctly ordered" ) ); if( lastNet != net ) { // New net found: MinConn = NOC; nextItemIdx = itemIdx; } switch( item->m_Type ) { // These items do not create erc problems case NETLIST_ITEM::ITEM_UNSPECIFIED: case NETLIST_ITEM::SEGMENT: case NETLIST_ITEM::BUS: case NETLIST_ITEM::JUNCTION: case NETLIST_ITEM::LABEL: case NETLIST_ITEM::BUSLABELMEMBER: case NETLIST_ITEM::PINLABEL: case NETLIST_ITEM::GLOBBUSLABELMEMBER: break; // TODO(JE) Port this to the new system case NETLIST_ITEM::PIN: { // Check if this pin has appeared before on a different net if( item->m_Link ) { auto ref = item->GetComponentParent()->GetRef( &item->m_SheetPath ); wxString pin_name = ref + "_" + item->m_PinNum; if( pin_to_net_map.count( pin_name ) == 0 ) { pin_to_net_map[pin_name] = item->GetNetName(); } else if( pin_to_net_map[pin_name] != item->GetNetName() ) { SCH_MARKER* marker = new SCH_MARKER(); marker->SetData( ERCE_DIFFERENT_UNIT_NET, item->m_Start, wxString::Format( _( "Pin %s on %s is connected to both %s and %s" ), item->m_PinNum, ref, pin_to_net_map[pin_name], item->GetNetName() ), item->m_Start ); marker->SetMarkerType( MARKER_BASE::MARKER_ERC ); marker->SetErrorLevel( MARKER_BASE::MARKER_SEVERITY_ERROR ); item->m_SheetPath.LastScreen()->Append( marker ); } } // Look for ERC problems between pins: TestOthersItems( objectsConnectedList.get(), itemIdx, nextItemIdx, &MinConn ); break; } default: break; } lastItemIdx = itemIdx; } // Test similar labels (i;e. labels which are identical when // using case insensitive comparisons) if( m_settings.check_similar_labels ) objectsConnectedList->TestforSimilarLabels(); // Displays global results: updateMarkerCounts( &screens ); // Display diags: DisplayERC_MarkersList(); // Display new markers from the current screen: KIGFX::VIEW* view = m_parent->GetCanvas()->GetView(); for( auto item : m_parent->GetScreen()->Items().OfType( SCH_MARKER_T ) ) view->Add( item ); m_parent->GetCanvas()->Refresh(); // Display message aReporter.ReportTail( _( "Finished" ), REPORTER::RPT_INFO ); if( m_settings.write_erc_file ) { fn = g_RootSheet->GetScreen()->GetFileName(); fn.SetExt( wxT( "erc" ) ); wxFileDialog dlg( this, _( "ERC File" ), fn.GetPath(), fn.GetFullName(), ErcFileWildcard(), wxFD_SAVE ); if( dlg.ShowModal() == wxID_CANCEL ) return; if( WriteDiagnosticERC( GetUserUnits(), dlg.GetPath() ) ) ExecuteFile( this, Pgm().GetEditorName(), QuoteFullPath( fn ) ); } } wxDialog* InvokeDialogERC( SCH_EDIT_FRAME* aCaller ) { // This is a modeless dialog, so new it rather than instantiating on stack. DIALOG_ERC* dlg = new DIALOG_ERC( aCaller ); dlg->Show( true ); return dlg; // wxDialog is information hiding about DIALOG_ERC. }