/* * 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) 2011 Wayne Stambaugh * Copyright (C) 1992-2024 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 "connection_graph.h" #include "kiface_ids.h" #include #include // for ExpandEnvVarSubstitutions #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* ERC tests : * 1 - conflicts between connected pins ( example: 2 connected outputs ) * 2 - minimal connections requirements ( 1 input *must* be connected to an * output, or a passive pin ) */ /* * Minimal ERC requirements: * All pins *must* be connected (except ELECTRICAL_PINTYPE::PT_NC). * When a pin is not connected in schematic, the user must place a "non * connected" symbol to this pin. * This ensures a forgotten connection will be detected. */ // Messages for matrix rows: const wxString CommentERC_H[] = { _( "Input Pin" ), _( "Output Pin" ), _( "Bidirectional Pin" ), _( "Tri-State Pin" ), _( "Passive Pin" ), _( "Free Pin" ), _( "Unspecified Pin" ), _( "Power Input Pin" ), _( "Power Output Pin" ), _( "Open Collector" ), _( "Open Emitter" ), _( "No Connection" ) }; // Messages for matrix columns const wxString CommentERC_V[] = { _( "Input Pin" ), _( "Output Pin" ), _( "Bidirectional Pin" ), _( "Tri-State Pin" ), _( "Passive Pin" ), _( "Free Pin" ), _( "Unspecified Pin" ), _( "Power Input Pin" ), _( "Power Output Pin" ), _( "Open Collector" ), _( "Open Emitter" ), _( "No Connection" ) }; // List of pin types that are considered drivers for usual input pins // i.e. pin type = ELECTRICAL_PINTYPE::PT_INPUT, but not PT_POWER_IN // that need only a PT_POWER_OUT pin type to be driven const std::set DrivingPinTypes = { ELECTRICAL_PINTYPE::PT_OUTPUT, ELECTRICAL_PINTYPE::PT_POWER_OUT, ELECTRICAL_PINTYPE::PT_PASSIVE, ELECTRICAL_PINTYPE::PT_TRISTATE, ELECTRICAL_PINTYPE::PT_BIDI }; // List of pin types that are considered drivers for power pins // In fact only a ELECTRICAL_PINTYPE::PT_POWER_OUT pin type can drive // power input pins const std::set DrivingPowerPinTypes = { ELECTRICAL_PINTYPE::PT_POWER_OUT }; // List of pin types that require a driver elsewhere on the net const std::set DrivenPinTypes = { ELECTRICAL_PINTYPE::PT_INPUT, ELECTRICAL_PINTYPE::PT_POWER_IN }; extern void CheckDuplicatePins( LIB_SYMBOL* aSymbol, std::vector& aMessages, UNITS_PROVIDER* aUnitsProvider ); int ERC_TESTER::TestDuplicateSheetNames( bool aCreateMarker ) { int err_count = 0; for( SCH_SCREEN* screen = m_screens.GetFirst(); screen; screen = m_screens.GetNext() ) { std::vector list; for( SCH_ITEM* item : screen->Items().OfType( SCH_SHEET_T ) ) list.push_back( static_cast( item ) ); for( size_t i = 0; i < list.size(); i++ ) { SCH_SHEET* sheet = list[i]; for( size_t j = i + 1; j < list.size(); j++ ) { SCH_SHEET* test_item = list[j]; // We have found a second sheet: compare names // we are using case insensitive comparison to avoid mistakes between // similar names like Mysheet and mysheet if( sheet->GetShownName( false ).IsSameAs( test_item->GetShownName( false ), false ) ) { if( aCreateMarker ) { auto ercItem = ERC_ITEM::Create( ERCE_DUPLICATE_SHEET_NAME ); ercItem->SetItems( sheet, test_item ); SCH_MARKER* marker = new SCH_MARKER( ercItem, sheet->GetPosition() ); screen->Append( marker ); } err_count++; } } } } return err_count; } void ERC_TESTER::TestTextVars( DS_PROXY_VIEW_ITEM* aDrawingSheet ) { DS_DRAW_ITEM_LIST wsItems( schIUScale ); auto unresolved = [this]( wxString str ) { str = ExpandEnvVarSubstitutions( str, &m_schematic->Prj() ); return str.Matches( wxS( "*${*}*" ) ); }; auto testAssertion = []( const SCH_ITEM* item, const SCH_SHEET_PATH& sheet, SCH_SCREEN* screen, const wxString& text ) { static wxRegEx warningExpr( wxS( "^\\$\\{ERC_WARNING\\s*([^}]*)\\}(.*)$" ) ); static wxRegEx errorExpr( wxS( "^\\$\\{ERC_ERROR\\s*([^}]*)\\}(.*)$" ) ); if( warningExpr.Matches( text ) ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_GENERIC_WARNING ); ercItem->SetItems( item ); ercItem->SetSheetSpecificPath( sheet ); ercItem->SetErrorMessage( warningExpr.GetMatch( text, 1 ) ); SCH_MARKER* marker = new SCH_MARKER( ercItem, item->GetPosition() ); screen->Append( marker ); } if( errorExpr.Matches( text ) ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_GENERIC_ERROR ); ercItem->SetItems( item ); ercItem->SetSheetSpecificPath( sheet ); ercItem->SetErrorMessage( errorExpr.GetMatch( text, 1 ) ); SCH_MARKER* marker = new SCH_MARKER( ercItem, item->GetPosition() ); screen->Append( marker ); } }; if( aDrawingSheet ) { wsItems.SetPageNumber( wxS( "1" ) ); wsItems.SetSheetCount( 1 ); wsItems.SetFileName( wxS( "dummyFilename" ) ); wsItems.SetSheetName( wxS( "dummySheet" ) ); wsItems.SetSheetLayer( wxS( "dummyLayer" ) ); wsItems.SetProject( &m_schematic->Prj() ); wsItems.BuildDrawItemsList( aDrawingSheet->GetPageInfo(), aDrawingSheet->GetTitleBlock() ); } for( const SCH_SHEET_PATH& sheet : m_sheetList ) { SCH_SCREEN* screen = sheet.LastScreen(); for( SCH_ITEM* item : screen->Items().OfType( SCH_LOCATE_ANY_T ) ) { if( item->Type() == SCH_SYMBOL_T ) { SCH_SYMBOL* symbol = static_cast( item ); for( SCH_FIELD& field : symbol->GetFields() ) { if( unresolved( field.GetShownText( &sheet, true ) ) ) { auto ercItem = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); ercItem->SetItems( symbol ); ercItem->SetSheetSpecificPath( sheet ); SCH_MARKER* marker = new SCH_MARKER( ercItem, field.GetPosition() ); screen->Append( marker ); } testAssertion( &field, sheet, screen, field.GetText() ); } symbol->GetLibSymbolRef()->RunOnChildren( [&]( SCH_ITEM* child ) { if( child->Type() == SCH_FIELD_T ) { // test only SCH_SYMBOL fields, not LIB_SYMBOL fields } else if( child->Type() == SCH_TEXT_T ) { SCH_TEXT* textItem = static_cast( child ); if( unresolved( textItem->GetShownText( &sheet, true ) ) ) { auto ercItem = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); ercItem->SetItems( symbol ); ercItem->SetSheetSpecificPath( sheet ); BOX2I bbox = textItem->GetBoundingBox(); bbox = symbol->GetTransform().TransformCoordinate( bbox ); VECTOR2I pos = bbox.Centre() + symbol->GetPosition(); SCH_MARKER* marker = new SCH_MARKER( ercItem, pos ); screen->Append( marker ); } testAssertion( symbol, sheet, screen, textItem->GetText() ); } else if( child->Type() == SCH_TEXTBOX_T ) { SCH_TEXTBOX* textboxItem = static_cast( child ); if( unresolved( textboxItem->GetShownText( &sheet, true ) ) ) { auto ercItem = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); ercItem->SetItems( symbol ); ercItem->SetSheetSpecificPath( sheet ); BOX2I bbox = textboxItem->GetBoundingBox(); bbox = symbol->GetTransform().TransformCoordinate( bbox ); VECTOR2I pos = bbox.Centre() + symbol->GetPosition(); SCH_MARKER* marker = new SCH_MARKER( ercItem, pos ); screen->Append( marker ); } testAssertion( symbol, sheet, screen, textboxItem->GetText() ); } } ); } else if( SCH_LABEL_BASE* label = dynamic_cast( item ) ) { for( SCH_FIELD& field : label->GetFields() ) { if( unresolved( field.GetShownText( &sheet, true ) ) ) { auto ercItem = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); ercItem->SetItems( label ); ercItem->SetSheetSpecificPath( sheet ); SCH_MARKER* marker = new SCH_MARKER( ercItem, field.GetPosition() ); screen->Append( marker ); } testAssertion( &field, sheet, screen, field.GetText() ); } } else if( item->Type() == SCH_SHEET_T ) { SCH_SHEET* subSheet = static_cast( item ); for( SCH_FIELD& field : subSheet->GetFields() ) { if( unresolved( field.GetShownText( &sheet, true ) ) ) { auto ercItem = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); ercItem->SetItems( subSheet ); ercItem->SetSheetSpecificPath( sheet ); SCH_MARKER* marker = new SCH_MARKER( ercItem, field.GetPosition() ); screen->Append( marker ); } testAssertion( &field, sheet, screen, field.GetText() ); } SCH_SHEET_PATH subSheetPath = sheet; subSheetPath.push_back( subSheet ); for( SCH_SHEET_PIN* pin : subSheet->GetPins() ) { if( pin->GetShownText( &subSheetPath, true ).Matches( wxS( "*${*}*" ) ) ) { auto ercItem = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); ercItem->SetItems( pin ); ercItem->SetSheetSpecificPath( sheet ); SCH_MARKER* marker = new SCH_MARKER( ercItem, pin->GetPosition() ); screen->Append( marker ); } } } else if( SCH_TEXT* text = dynamic_cast( item ) ) { if( text->GetShownText( &sheet, true ).Matches( wxS( "*${*}*" ) ) ) { auto ercItem = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); ercItem->SetItems( text ); ercItem->SetSheetSpecificPath( sheet ); SCH_MARKER* marker = new SCH_MARKER( ercItem, text->GetPosition() ); screen->Append( marker ); } testAssertion( text, sheet, screen, text->GetText() ); } else if( SCH_TEXTBOX* textBox = dynamic_cast( item ) ) { if( textBox->GetShownText( &sheet, true ).Matches( wxS( "*${*}*" ) ) ) { auto ercItem = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); ercItem->SetItems( textBox ); ercItem->SetSheetSpecificPath( sheet ); SCH_MARKER* marker = new SCH_MARKER( ercItem, textBox->GetPosition() ); screen->Append( marker ); } testAssertion( textBox, sheet, screen, textBox->GetText() ); } } for( DS_DRAW_ITEM_BASE* item = wsItems.GetFirst(); item; item = wsItems.GetNext() ) { if( DS_DRAW_ITEM_TEXT* text = dynamic_cast( item ) ) { if( text->GetShownText( true ).Matches( wxS( "*${*}*" ) ) ) { std::shared_ptr erc = ERC_ITEM::Create( ERCE_UNRESOLVED_VARIABLE ); erc->SetErrorMessage( _( "Unresolved text variable in drawing sheet" ) ); erc->SetSheetSpecificPath( sheet ); SCH_MARKER* marker = new SCH_MARKER( erc, text->GetPosition() ); screen->Append( marker ); } } } } } int ERC_TESTER::TestConflictingBusAliases() { wxString msg; int err_count = 0; std::vector> aliases; for( SCH_SCREEN* screen = m_screens.GetFirst(); screen; screen = m_screens.GetNext() ) { const auto& screen_aliases = screen->GetBusAliases(); for( const std::shared_ptr& alias : screen_aliases ) { std::vector aliasMembers = alias->Members(); std::sort( aliasMembers.begin(), aliasMembers.end() ); for( const std::shared_ptr& test : aliases ) { std::vector testMembers = test->Members(); std::sort( testMembers.begin(), testMembers.end() ); if( alias->GetName() == test->GetName() && aliasMembers != testMembers ) { msg.Printf( _( "Bus alias %s has conflicting definitions on %s and %s" ), alias->GetName(), alias->GetParent()->GetFileName(), test->GetParent()->GetFileName() ); std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_BUS_ALIAS_CONFLICT ); ercItem->SetErrorMessage( msg ); SCH_MARKER* marker = new SCH_MARKER( ercItem, VECTOR2I() ); test->GetParent()->Append( marker ); ++err_count; } } } aliases.insert( aliases.end(), screen_aliases.begin(), screen_aliases.end() ); } return err_count; } int ERC_TESTER::TestMultiunitFootprints() { int errors = 0; for( std::pair& symbol : m_refMap ) { SCH_REFERENCE_LIST& refList = symbol.second; if( refList.GetCount() == 0 ) { wxFAIL; // it should not happen continue; } // Reference footprint SCH_SYMBOL* unit = nullptr; wxString unitName; wxString unitFP; for( size_t ii = 0; ii < refList.GetCount(); ++ii ) { SCH_SHEET_PATH sheetPath = refList.GetItem( ii ).GetSheetPath(); unitFP = refList.GetItem( ii ).GetFootprint(); if( !unitFP.IsEmpty() ) { unit = refList.GetItem( ii ).GetSymbol(); unitName = unit->GetRef( &sheetPath, true ); break; } } for( size_t ii = 0; ii < refList.GetCount(); ++ii ) { SCH_REFERENCE& secondRef = refList.GetItem( ii ); SCH_SYMBOL* secondUnit = secondRef.GetSymbol(); wxString secondName = secondUnit->GetRef( &secondRef.GetSheetPath(), true ); const wxString secondFp = secondRef.GetFootprint(); wxString msg; if( unit && !secondFp.IsEmpty() && unitFP != secondFp ) { msg.Printf( _( "Different footprints assigned to %s and %s" ), unitName, secondName ); std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_DIFFERENT_UNIT_FP ); ercItem->SetErrorMessage( msg ); ercItem->SetItems( unit, secondUnit ); SCH_MARKER* marker = new SCH_MARKER( ercItem, secondUnit->GetPosition() ); secondRef.GetSheetPath().LastScreen()->Append( marker ); ++errors; } } } return errors; } int ERC_TESTER::TestMissingUnits() { int errors = 0; for( std::pair& symbol : m_refMap ) { SCH_REFERENCE_LIST& refList = symbol.second; wxCHECK2( refList.GetCount(), continue ); // Reference unit SCH_REFERENCE& base_ref = refList.GetItem( 0 ); SCH_SYMBOL* unit = base_ref.GetSymbol(); LIB_SYMBOL* libSymbol = base_ref.GetLibPart(); if( static_cast( refList.GetCount() ) == libSymbol->GetUnitCount() ) continue; std::set lib_units; std::set instance_units; std::set missing_units; auto report = [&]( std::set& aMissingUnits, const wxString& aErrorMsg, int aErrorCode ) { wxString msg; wxString missing_pin_units = wxS( "[ " ); int ii = 0; for( int missing_unit : aMissingUnits ) { if( ii++ == 3 ) { missing_pin_units += wxS( "....." ); break; } missing_pin_units += libSymbol->GetUnitDisplayName( missing_unit ) + ", " ; } missing_pin_units.Truncate( missing_pin_units.length() - 2 ); missing_pin_units += wxS( " ]" ); msg.Printf( aErrorMsg, symbol.first, missing_pin_units ); std::shared_ptr ercItem = ERC_ITEM::Create( aErrorCode ); ercItem->SetErrorMessage( msg ); ercItem->SetItems( unit ); ercItem->SetSheetSpecificPath( base_ref.GetSheetPath() ); ercItem->SetItemsSheetPaths( base_ref.GetSheetPath() ); SCH_MARKER* marker = new SCH_MARKER( ercItem, unit->GetPosition() ); base_ref.GetSheetPath().LastScreen()->Append( marker ); ++errors; }; for( int ii = 1; ii <= libSymbol->GetUnitCount(); ++ii ) lib_units.insert( lib_units.end(), ii ); for( size_t ii = 0; ii < refList.GetCount(); ++ii ) instance_units.insert( instance_units.end(), refList.GetItem( ii ).GetUnit() ); std::set_difference( lib_units.begin(), lib_units.end(), instance_units.begin(), instance_units.end(), std::inserter( missing_units, missing_units.begin() ) ); if( !missing_units.empty() && m_settings.IsTestEnabled( ERCE_MISSING_UNIT ) ) { report( missing_units, _( "Symbol %s has unplaced units %s" ), ERCE_MISSING_UNIT ); } std::set missing_power; std::set missing_input; std::set missing_bidi; for( int missing_unit : missing_units ) { int bodyStyle = 0; for( size_t ii = 0; ii < refList.GetCount(); ++ii ) { if( refList.GetItem( ii ).GetUnit() == missing_unit ) { bodyStyle = refList.GetItem( ii ).GetSymbol()->GetBodyStyle(); break; } } for( SCH_PIN* pin : libSymbol->GetPins( missing_unit, bodyStyle ) ) { switch( pin->GetType() ) { case ELECTRICAL_PINTYPE::PT_POWER_IN: missing_power.insert( missing_unit ); break; case ELECTRICAL_PINTYPE::PT_BIDI: missing_bidi.insert( missing_unit ); break; case ELECTRICAL_PINTYPE::PT_INPUT: missing_input.insert( missing_unit ); break; default: break; } } } if( !missing_power.empty() && m_settings.IsTestEnabled( ERCE_MISSING_POWER_INPUT_PIN ) ) { report( missing_power, _( "Symbol %s has input power pins in units %s that are not placed." ), ERCE_MISSING_POWER_INPUT_PIN ); } if( !missing_input.empty() && m_settings.IsTestEnabled( ERCE_MISSING_INPUT_PIN ) ) { report( missing_input, _( "Symbol %s has input pins in units %s that are not placed." ), ERCE_MISSING_INPUT_PIN ); } if( !missing_bidi.empty() && m_settings.IsTestEnabled( ERCE_MISSING_BIDI_PIN ) ) { report( missing_bidi, _( "Symbol %s has bidirectional pins in units %s that are not placed." ), ERCE_MISSING_BIDI_PIN ); } } return errors; } int ERC_TESTER::TestMissingNetclasses() { int err_count = 0; std::shared_ptr& settings = m_schematic->Prj().GetProjectFile().NetSettings(); wxString defaultNetclass = settings->GetDefaultNetclass()->GetName(); auto logError = [&]( const SCH_SHEET_PATH& sheet, SCH_ITEM* item, const wxString& netclass ) { err_count++; std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_UNDEFINED_NETCLASS ); ercItem->SetItems( item ); ercItem->SetErrorMessage( wxString::Format( _( "Netclass %s is not defined" ), netclass ) ); SCH_MARKER* marker = new SCH_MARKER( ercItem, item->GetPosition() ); sheet.LastScreen()->Append( marker ); }; for( const SCH_SHEET_PATH& sheet : m_sheetList ) { for( SCH_ITEM* item : sheet.LastScreen()->Items() ) { item->RunOnChildren( [&]( SCH_ITEM* aChild ) { if( aChild->Type() == SCH_FIELD_T ) { SCH_FIELD* field = static_cast( aChild ); if( field->GetCanonicalName() == wxT( "Netclass" ) ) { wxString netclass = field->GetShownText( &sheet, false ); if( !netclass.IsSameAs( defaultNetclass ) && !settings->HasNetclass( netclass ) ) { logError( sheet, item, netclass ); } } } return true; } ); } } return err_count; } int ERC_TESTER::TestLabelMultipleWires() { int err_count = 0; for( const SCH_SHEET_PATH& sheet : m_sheetList ) { std::map> connMap; for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_LABEL_T ) ) { SCH_LABEL* label = static_cast( item ); for( const VECTOR2I& pt : label->GetConnectionPoints() ) connMap[pt].emplace_back( label ); } for( const std::pair>& pair : connMap ) { std::vector lines; for( SCH_ITEM* item : sheet.LastScreen()->Items().Overlapping( SCH_LINE_T, pair.first ) ) { SCH_LINE* line = static_cast( item ); if( line->IsGraphicLine() ) continue; // If the line is connected at the endpoint, then there will be a junction if( !line->IsEndPoint( pair.first ) ) lines.emplace_back( line ); } if( lines.size() > 1 ) { err_count++; lines.resize( 3 ); // Only show the first 3 lines and if there are only two, adds a nullptr std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_LABEL_MULTIPLE_WIRES ); wxString msg = wxString::Format( _( "Label connects more than one wire at %d, %d" ), pair.first.x, pair.first.y ); ercItem->SetItems( pair.second.front(), lines[0], lines[1], lines[2] ); ercItem->SetErrorMessage( msg ); ercItem->SetSheetSpecificPath( sheet ); SCH_MARKER* marker = new SCH_MARKER( ercItem, pair.first ); sheet.LastScreen()->Append( marker ); } } } return err_count; } int ERC_TESTER::TestFourWayJunction() { int err_count = 0; for( const SCH_SHEET_PATH& sheet : m_sheetList ) { std::map> connMap; SCH_SCREEN* screen = sheet.LastScreen(); for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); for( SCH_PIN* pin : symbol->GetPins( &sheet ) ) connMap[pin->GetPosition()].emplace_back( pin ); } for( SCH_ITEM* item : screen->Items().OfType( SCH_LINE_T ) ) { SCH_LINE* line = static_cast( item ); if( line->IsGraphicLine() ) continue; for( const VECTOR2I& pt : line->GetConnectionPoints() ) connMap[pt].emplace_back( line ); } for( const std::pair>& pair : connMap ) { if( pair.second.size() >= 4 ) { err_count++; std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_FOUR_WAY_JUNCTION ); ercItem->SetItems( pair.second[0], pair.second[1], pair.second[2], pair.second[3] ); wxString msg = wxString::Format( _( "Four items connected at %d, %d" ), pair.first.x, pair.first.y ); ercItem->SetErrorMessage( msg ); ercItem->SetSheetSpecificPath( sheet ); SCH_MARKER* marker = new SCH_MARKER( ercItem, pair.first ); sheet.LastScreen()->Append( marker ); } } } return err_count; } int ERC_TESTER::TestNoConnectPins() { int err_count = 0; for( const SCH_SHEET_PATH& sheet : m_sheetList ) { std::map> pinMap; auto addOther = [&]( const VECTOR2I& pt, SCH_ITEM* aOther ) { if( pinMap.count( pt ) ) pinMap[pt].emplace_back( aOther ); }; for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); for( SCH_PIN* pin : symbol->GetPins( &sheet ) ) { if( pin->GetLibPin()->GetType() == ELECTRICAL_PINTYPE::PT_NC ) pinMap[pin->GetPosition()].emplace_back( pin ); } } for( SCH_ITEM* item : sheet.LastScreen()->Items() ) { if( item->Type() == SCH_SYMBOL_T ) { SCH_SYMBOL* symbol = static_cast( item ); for( SCH_PIN* pin : symbol->GetPins( &sheet ) ) { if( pin->GetLibPin()->GetType() != ELECTRICAL_PINTYPE::PT_NC ) addOther( pin->GetPosition(), pin ); } } else if( item->IsConnectable() ) { for( const VECTOR2I& pt : item->GetConnectionPoints() ) addOther( pt, item ); } } for( const std::pair>& pair : pinMap ) { if( pair.second.size() > 1 ) { err_count++; std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_NOCONNECT_CONNECTED ); ercItem->SetItems( pair.second[0], pair.second[1], pair.second.size() > 2 ? pair.second[2] : nullptr, pair.second.size() > 3 ? pair.second[3] : nullptr ); ercItem->SetErrorMessage( _( "Pin with 'no connection' type is connected" ) ); ercItem->SetSheetSpecificPath( sheet ); SCH_MARKER* marker = new SCH_MARKER( ercItem, pair.first ); sheet.LastScreen()->Append( marker ); } } } return err_count; } int ERC_TESTER::TestPinToPin() { int errors = 0; for( const std::pair> net : m_nets ) { using iterator_t = std::vector::iterator; std::vector pins; std::unordered_map pinToScreenMap; bool has_noconnect = false; for( CONNECTION_SUBGRAPH* subgraph: net.second ) { if( subgraph->GetNoConnect() ) has_noconnect = true; for( SCH_ITEM* item : subgraph->GetItems() ) { if( item->Type() == SCH_PIN_T ) { pins.emplace_back( static_cast( item ), subgraph->GetSheet() ); pinToScreenMap[item] = subgraph->GetSheet().LastScreen(); } } } std::sort( pins.begin(), pins.end(), []( const ERC_SCH_PIN_CONTEXT& lhs, const ERC_SCH_PIN_CONTEXT& rhs ) { int ret = StrNumCmp( lhs.Pin()->GetParentSymbol()->GetRef( &lhs.Sheet() ), rhs.Pin()->GetParentSymbol()->GetRef( &rhs.Sheet() ) ); if( ret == 0 ) ret = StrNumCmp( lhs.Pin()->GetNumber(), rhs.Pin()->GetNumber() ); if( ret == 0 ) ret = lhs < rhs; // Fallback to hash to guarantee deterministic sort return ret < 0; } ); ERC_SCH_PIN_CONTEXT needsDriver; ELECTRICAL_PINTYPE needsDriverType = ELECTRICAL_PINTYPE::PT_UNSPECIFIED; bool hasDriver = false; // We need different drivers for power nets and normal nets. // A power net has at least one pin having the ELECTRICAL_PINTYPE::PT_POWER_IN // and power nets can be driven only by ELECTRICAL_PINTYPE::PT_POWER_OUT pins bool ispowerNet = false; for( ERC_SCH_PIN_CONTEXT& refPin : pins ) { if( refPin.Pin()->GetType() == ELECTRICAL_PINTYPE::PT_POWER_IN ) { ispowerNet = true; break; } } std::vector> pin_mismatches; std::map pin_mismatch_counts; for( auto refIt = pins.begin(); refIt != pins.end(); ++refIt ) { ERC_SCH_PIN_CONTEXT& refPin = *refIt; ELECTRICAL_PINTYPE refType = refPin.Pin()->GetType(); if( DrivenPinTypes.contains( refType ) ) { // needsDriver will be the pin shown in the error report eventually, so try to // upgrade to a "better" pin if possible: something visible and only a power symbol // if this net needs a power driver if( !needsDriver.Pin() || ( !needsDriver.Pin()->IsVisible() && refPin.Pin()->IsVisible() ) || ( ispowerNet != ( needsDriverType == ELECTRICAL_PINTYPE::PT_POWER_IN ) && ispowerNet == ( refType == ELECTRICAL_PINTYPE::PT_POWER_IN ) ) ) { needsDriver = refPin; needsDriverType = needsDriver.Pin()->GetType(); } } if( ispowerNet ) hasDriver |= ( DrivingPowerPinTypes.count( refType ) != 0 ); else hasDriver |= ( DrivingPinTypes.count( refType ) != 0 ); for( auto testIt = refIt + 1; testIt != pins.end(); ++testIt ) { ERC_SCH_PIN_CONTEXT& testPin = *testIt; // Multiple pins in the same symbol that share a type, // name and position are considered // "stacked" and shouldn't trigger ERC errors if( refPin.Pin()->IsStacked( testPin.Pin() ) && refPin.Sheet() == testPin.Sheet() ) continue; ELECTRICAL_PINTYPE testType = testPin.Pin()->GetType(); if( ispowerNet ) hasDriver |= DrivingPowerPinTypes.contains( testType ); else hasDriver |= DrivingPinTypes.contains( testType ); PIN_ERROR erc = m_settings.GetPinMapValue( refType, testType ); if( erc != PIN_ERROR::OK && m_settings.IsTestEnabled( ERCE_PIN_TO_PIN_WARNING ) ) { pin_mismatches.emplace_back( std::tuple{ refIt, testIt, erc } ); if( m_settings.GetERCSortingMetric() == ERC_PIN_SORTING_METRIC::SM_HEURISTICS ) { pin_mismatch_counts[refIt] = m_settings.GetPinTypeWeight( ( *refIt ).Pin()->GetType() ); pin_mismatch_counts[testIt] = m_settings.GetPinTypeWeight( ( *testIt ).Pin()->GetType() ); } else { if( !pin_mismatch_counts.contains( testIt ) ) pin_mismatch_counts.emplace( testIt, 1 ); else pin_mismatch_counts[testIt]++; if( !pin_mismatch_counts.contains( refIt ) ) pin_mismatch_counts.emplace( refIt, 1 ); else pin_mismatch_counts[refIt]++; } } } } std::multimap> pins_dsc; std::transform( pin_mismatch_counts.begin(), pin_mismatch_counts.end(), std::inserter( pins_dsc, pins_dsc.begin() ), []( const auto& p ) { return std::pair( p.second, p.first ); } ); for( const auto& [amount, pinIt] : pins_dsc ) { if( pin_mismatches.empty() ) break; SCH_PIN* pin = ( *pinIt ).Pin(); VECTOR2I position = pin->GetPosition(); iterator_t nearest_pin = pins.end(); double smallest_distance = std::numeric_limits::infinity(); PIN_ERROR erc; std::erase_if( pin_mismatches, [&]( const auto& tuple ) { iterator_t other; if( pinIt == std::get<0>( tuple ) ) other = std::get<1>( tuple ); else if( pinIt == std::get<1>( tuple ) ) other = std::get<0>( tuple ); else return false; if( ( *pinIt ).Sheet().Cmp( ( *other ).Sheet() ) != 0 ) { if( std::isinf( smallest_distance ) ) { nearest_pin = other; erc = std::get<2>( tuple ); } } else { double distance = position.Distance( ( *other ).Pin()->GetPosition() ); if( std::isinf( smallest_distance ) || distance < smallest_distance ) { smallest_distance = distance; nearest_pin = other; erc = std::get<2>( tuple ); } } return true; } ); if( nearest_pin != pins.end() ) { SCH_PIN* other_pin = ( *nearest_pin ).Pin(); std::shared_ptr ercItem = ERC_ITEM::Create( erc == PIN_ERROR::WARNING ? ERCE_PIN_TO_PIN_WARNING : ERCE_PIN_TO_PIN_ERROR ); ercItem->SetItems( pin, other_pin ); ercItem->SetSheetSpecificPath( ( *pinIt ).Sheet() ); ercItem->SetItemsSheetPaths( ( *pinIt ).Sheet(), ( *nearest_pin ).Sheet() ); ercItem->SetErrorMessage( wxString::Format( _( "Pins of type %s and %s are connected" ), ElectricalPinTypeGetText( pin->GetType() ), ElectricalPinTypeGetText( other_pin->GetType() ) ) ); SCH_MARKER* marker = new SCH_MARKER( ercItem, pin->GetPosition() ); pinToScreenMap[pin]->Append( marker ); errors++; } } if( needsDriver.Pin() && !hasDriver && !has_noconnect ) { int err_code = ispowerNet ? ERCE_POWERPIN_NOT_DRIVEN : ERCE_PIN_NOT_DRIVEN; if( m_settings.IsTestEnabled( err_code ) ) { std::shared_ptr ercItem = ERC_ITEM::Create( err_code ); ercItem->SetItems( needsDriver.Pin() ); ercItem->SetSheetSpecificPath( needsDriver.Sheet() ); ercItem->SetItemsSheetPaths( needsDriver.Sheet() ); SCH_MARKER* marker = new SCH_MARKER( ercItem, needsDriver.Pin()->GetPosition() ); pinToScreenMap[needsDriver.Pin()]->Append( marker ); errors++; } } } return errors; } int ERC_TESTER::TestMultUnitPinConflicts() { int errors = 0; std::unordered_map> pinToNetMap; for( const std::pair> net : m_nets ) { const wxString& netName = net.first.Name; for( CONNECTION_SUBGRAPH* subgraph : net.second ) { for( SCH_ITEM* item : subgraph->GetItems() ) { if( item->Type() == SCH_PIN_T ) { SCH_PIN* pin = static_cast( item ); const SCH_SHEET_PATH& sheet = subgraph->GetSheet(); if( !pin->GetLibPin()->GetParentSymbol()->IsMulti() ) continue; wxString name = pin->GetParentSymbol()->GetRef( &sheet ) + + ":" + pin->GetShownNumber(); if( !pinToNetMap.count( name ) ) { pinToNetMap[name] = std::make_pair( netName, pin ); } else if( pinToNetMap[name].first != netName ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_DIFFERENT_UNIT_NET ); ercItem->SetErrorMessage( wxString::Format( _( "Pin %s is connected to both %s and %s" ), pin->GetShownNumber(), netName, pinToNetMap[name].first ) ); ercItem->SetItems( pin, pinToNetMap[name].second ); ercItem->SetSheetSpecificPath( sheet ); ercItem->SetItemsSheetPaths( sheet, sheet ); SCH_MARKER* marker = new SCH_MARKER( ercItem, pin->GetPosition() ); sheet.LastScreen()->Append( marker ); errors += 1; } } } } } return errors; } int ERC_TESTER::TestSameLocalGlobalLabel() { int errCount = 0; std::unordered_map> globalLabels; std::unordered_map> localLabels; for( const std::pair> net : m_nets ) { for( CONNECTION_SUBGRAPH* subgraph : net.second ) { const SCH_SHEET_PATH& sheet = subgraph->GetSheet(); for( SCH_ITEM* item : subgraph->GetItems() ) { if( item->Type() == SCH_LABEL_T || item->Type() == SCH_GLOBAL_LABEL_T ) { SCH_LABEL_BASE* label = static_cast( item ); wxString text = label->GetShownText( &sheet, false ); auto& map = item->Type() == SCH_LABEL_T ? localLabels : globalLabels; if( !map.count( text ) ) { map[text] = std::make_pair( label, sheet ); } } } } } for( auto& [globalText, globalItem] : globalLabels ) { for( auto& [localText, localItem] : localLabels ) { if( globalText == localText ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_SAME_LOCAL_GLOBAL_LABEL ); ercItem->SetItems( globalItem.first, localItem.first ); ercItem->SetSheetSpecificPath( globalItem.second ); ercItem->SetItemsSheetPaths( globalItem.second, localItem.second ); SCH_MARKER* marker = new SCH_MARKER( ercItem, globalItem.first->GetPosition() ); globalItem.second.LastScreen()->Append( marker ); errCount++; } } } return errCount; } int ERC_TESTER::TestSimilarLabels() { int errors = 0; std::unordered_map> generalMap; auto logError = [&]( const wxString& normalized, SCH_ITEM* item, const SCH_SHEET_PATH& sheet ) { auto& [otherText, otherItem, otherSheet] = generalMap.at( normalized ); ERCE_T typeOfWarning = ERCE_SIMILAR_LABELS; if( item->Type() == SCH_PIN_T && otherItem->Type() == SCH_PIN_T ) { //Two Pins typeOfWarning = ERCE_SIMILAR_POWER; } else if( item->Type() == SCH_PIN_T || otherItem->Type() == SCH_PIN_T ) { //Pin and Label typeOfWarning = ERCE_SIMILAR_LABEL_AND_POWER; } else { //Two Labels typeOfWarning = ERCE_SIMILAR_LABELS; } std::shared_ptr ercItem = ERC_ITEM::Create( typeOfWarning ); ercItem->SetItems( item, otherItem ); ercItem->SetSheetSpecificPath( sheet ); ercItem->SetItemsSheetPaths( sheet, otherSheet ); SCH_MARKER* marker = new SCH_MARKER( ercItem, item->GetPosition() ); sheet.LastScreen()->Append( marker ); }; for( const std::pair> net : m_nets ) { for( CONNECTION_SUBGRAPH* subgraph : net.second ) { const SCH_SHEET_PATH& sheet = subgraph->GetSheet(); for( SCH_ITEM* item : subgraph->GetItems() ) { switch( item->Type() ) { case SCH_LABEL_T: case SCH_HIER_LABEL_T: case SCH_GLOBAL_LABEL_T: { SCH_LABEL_BASE* label = static_cast( item ); wxString unnormalized = label->GetShownText( &sheet, false ); wxString normalized = unnormalized.Lower(); if( !generalMap.count( normalized ) ) { generalMap[normalized] = std::make_tuple( unnormalized, label, sheet ); } auto& [otherText, otherItem, otherSheet] = generalMap.at( normalized ); if( unnormalized != otherText ) { logError( normalized, label, sheet ); errors += 1; } break; } case SCH_PIN_T: { SCH_PIN* pin = static_cast( item ); if( !pin->IsGlobalPower() ) { continue; } SCH_SYMBOL* symbol = static_cast( pin->GetParentSymbol() ); wxString unnormalized = symbol->GetValue( true, &sheet, false ); wxString normalized = unnormalized.Lower(); if( !generalMap.count( normalized ) ) { generalMap[normalized] = std::make_tuple( unnormalized, pin, sheet ); } auto& [otherText, otherItem, otherSheet] = generalMap.at( normalized ); if( unnormalized != otherText ) { logError( normalized, pin, sheet ); errors += 1; } break; } default: break; } } } } return errors; } int ERC_TESTER::TestLibSymbolIssues() { wxCHECK( m_schematic, 0 ); SYMBOL_LIB_TABLE* libTable = PROJECT_SCH::SchSymbolLibTable( &m_schematic->Prj() ); wxString msg; int err_count = 0; for( SCH_SCREEN* screen = m_screens.GetFirst(); screen; screen = m_screens.GetNext() ) { std::vector markers; for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); LIB_SYMBOL* libSymbolInSchematic = symbol->GetLibSymbolRef().get(); wxCHECK2( libSymbolInSchematic, continue ); wxString libName = symbol->GetLibId().GetLibNickname(); LIB_TABLE_ROW* libTableRow = libTable->FindRow( libName, true ); if( !libTableRow ) { if( m_settings.IsTestEnabled( ERCE_LIB_SYMBOL_ISSUES ) ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_LIB_SYMBOL_ISSUES ); ercItem->SetItems( symbol ); msg.Printf( _( "The current configuration does not include the symbol library '%s'" ), UnescapeString( libName ) ); ercItem->SetErrorMessage( msg ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); } continue; } else if( !libTable->HasLibrary( libName, true ) ) { if( m_settings.IsTestEnabled( ERCE_LIB_SYMBOL_ISSUES ) ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_LIB_SYMBOL_ISSUES ); ercItem->SetItems( symbol ); msg.Printf( _( "The library '%s' is not enabled in the current configuration" ), UnescapeString( libName ) ); ercItem->SetErrorMessage( msg ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); } continue; } wxString symbolName = symbol->GetLibId().GetLibItemName(); LIB_SYMBOL* libSymbol = SchGetLibSymbol( symbol->GetLibId(), libTable ); if( libSymbol == nullptr ) { if( m_settings.IsTestEnabled( ERCE_LIB_SYMBOL_ISSUES ) ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_LIB_SYMBOL_ISSUES ); ercItem->SetItems( symbol ); msg.Printf( _( "Symbol '%s' not found in symbol library '%s'" ), UnescapeString( symbolName ), UnescapeString( libName ) ); ercItem->SetErrorMessage( msg ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); } continue; } std::unique_ptr flattenedSymbol = libSymbol->Flatten(); constexpr int flags = SCH_ITEM::COMPARE_FLAGS::EQUALITY | SCH_ITEM::COMPARE_FLAGS::ERC; if( m_settings.IsTestEnabled( ERCE_LIB_SYMBOL_MISMATCH ) ) { // We have to check for duplicate pins first as they will cause Compare() to fail. std::vector messages; UNITS_PROVIDER unitsProvider( schIUScale, EDA_UNITS::MILS ); CheckDuplicatePins( libSymbolInSchematic, messages, &unitsProvider ); if( !messages.empty() ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_DUPLICATE_PIN_ERROR ); ercItem->SetItems( symbol ); msg.Printf( _( "Symbol '%s' has multiple pins with the same pin number" ), UnescapeString( symbolName ) ); ercItem->SetErrorMessage( msg ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); } else if( flattenedSymbol->Compare( *libSymbolInSchematic, flags ) != 0 ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_LIB_SYMBOL_MISMATCH ); ercItem->SetItems( symbol ); msg.Printf( _( "Symbol '%s' doesn't match copy in library '%s'" ), UnescapeString( symbolName ), UnescapeString( libName ) ); ercItem->SetErrorMessage( msg ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); } } } for( SCH_MARKER* marker : markers ) { screen->Append( marker ); err_count += 1; } } return err_count; } int ERC_TESTER::TestFootprintLinkIssues( KIFACE* aCvPcb, PROJECT* aProject ) { wxCHECK( m_schematic, 0 ); wxString msg; int err_count = 0; typedef int (*TESTER_FN_PTR)( const wxString&, PROJECT* ); TESTER_FN_PTR linkTester = (TESTER_FN_PTR) aCvPcb->IfaceOrAddress( KIFACE_TEST_FOOTPRINT_LINK ); for( SCH_SHEET_PATH& sheet : m_sheetList ) { std::vector markers; for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); wxString footprint = symbol->GetFootprintFieldText( true, &sheet, false ); if( footprint.IsEmpty() ) continue; LIB_ID fpID; if( fpID.Parse( footprint, true ) >= 0 ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_FOOTPRINT_LINK_ISSUES ); msg.Printf( _( "'%s' is not a valid footprint identifier." ), footprint ); ercItem->SetErrorMessage( msg ); ercItem->SetItems( symbol ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); continue; } wxString libName = fpID.GetLibNickname(); wxString fpName = fpID.GetLibItemName(); int ret = (linkTester)( footprint, aProject ); if( ret == KIFACE_TEST_FOOTPRINT_LINK_NO_LIBRARY ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_FOOTPRINT_LINK_ISSUES ); msg.Printf( _( "The current configuration does not include the footprint library '%s'." ), libName ); ercItem->SetErrorMessage( msg ); ercItem->SetItems( symbol ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); } else if( ret == KIFACE_TEST_FOOTPRINT_LINK_LIBRARY_NOT_ENABLED ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_FOOTPRINT_LINK_ISSUES ); msg.Printf( _( "The footprint library '%s' is not enabled in the current configuration." ), libName ); ercItem->SetErrorMessage( msg ); ercItem->SetItems( symbol ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); } else if( ret == KIFACE_TEST_FOOTPRINT_LINK_NO_FOOTPRINT ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_FOOTPRINT_LINK_ISSUES ); msg.Printf( _( "Footprint '%s' not found in library '%s'." ), fpName, libName ); ercItem->SetErrorMessage( msg ); ercItem->SetItems( symbol ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); } } for( SCH_MARKER* marker : markers ) { sheet.LastScreen()->Append( marker ); err_count += 1; } } return err_count; } int ERC_TESTER::TestFootprintFilters() { wxCHECK( m_schematic, 0 ); wxString msg; int err_count = 0; for( SCH_SHEET_PATH& sheet : m_sheetList ) { std::vector markers; for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* sch_symbol = static_cast( item ); std::unique_ptr& lib_symbol = sch_symbol->GetLibSymbolRef(); if( !lib_symbol ) continue; wxArrayString filters = lib_symbol->GetFPFilters(); if( filters.empty() ) continue; LIB_ID footprint; if( footprint.Parse( sch_symbol->GetFootprintFieldText( true, &sheet, false ) ) > 0 ) continue; wxString footprintName = footprint.GetUniStringLibItemName(); bool found = false; for( const wxString& filter : filters ) { found |= footprintName.Matches( filter ); if( found ) break; } if( !found ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_FOOTPRINT_LINK_ISSUES ); msg.Printf( _( "Assigned footprint (%s) doesn't match footprint filters (%s)." ), footprintName, wxJoin( filters, ' ' ) ); ercItem->SetErrorMessage( msg ); ercItem->SetItems( sch_symbol ); markers.emplace_back( new SCH_MARKER( ercItem, sch_symbol->GetPosition() ) ); } } for( SCH_MARKER* marker : markers ) { sheet.LastScreen()->Append( marker ); err_count += 1; } } return err_count; } int ERC_TESTER::TestOffGridEndpoints() { const int gridSize = m_schematic->Settings().m_ConnectionGridSize; int err_count = 0; for( SCH_SCREEN* screen = m_screens.GetFirst(); screen; screen = m_screens.GetNext() ) { std::vector markers; for( SCH_ITEM* item : screen->Items() ) { if( item->Type() == SCH_LINE_T && item->IsConnectable() ) { SCH_LINE* line = static_cast( item ); if( ( line->GetStartPoint().x % gridSize ) != 0 || ( line->GetStartPoint().y % gridSize ) != 0 ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_ENDPOINT_OFF_GRID ); ercItem->SetItems( line ); markers.emplace_back( new SCH_MARKER( ercItem, line->GetStartPoint() ) ); } else if( ( line->GetEndPoint().x % gridSize ) != 0 || ( line->GetEndPoint().y % gridSize ) != 0 ) { std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_ENDPOINT_OFF_GRID ); ercItem->SetItems( line ); markers.emplace_back( new SCH_MARKER( ercItem, line->GetEndPoint() ) ); } } else if( item->Type() == SCH_SYMBOL_T ) { SCH_SYMBOL* symbol = static_cast( item ); for( SCH_PIN* pin : symbol->GetPins( nullptr ) ) { VECTOR2I pinPos = pin->GetPosition(); if( ( pinPos.x % gridSize ) != 0 || ( pinPos.y % gridSize ) != 0 ) { auto ercItem = ERC_ITEM::Create( ERCE_ENDPOINT_OFF_GRID ); ercItem->SetItems( pin ); markers.emplace_back( new SCH_MARKER( ercItem, pinPos ) ); break; } } } } for( SCH_MARKER* marker : markers ) { screen->Append( marker ); err_count += 1; } } return err_count; } int ERC_TESTER::TestSimModelIssues() { WX_STRING_REPORTER reporter; int err_count = 0; SIM_LIB_MGR libMgr( &m_schematic->Prj() ); for( SCH_SHEET_PATH& sheet : m_sheetList ) { if( sheet.GetExcludedFromSim() ) continue; std::vector markers; for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) ) { SCH_SYMBOL* symbol = static_cast( item ); // Power symbols and other symbols which have the reference starting with "#" are // not included in simulation if( symbol->GetRef( &sheet ).StartsWith( '#' ) || symbol->GetExcludedFromSim() ) continue; // Reset for each symbol reporter.Clear(); SIM_LIBRARY::MODEL model = libMgr.CreateModel( &sheet, *symbol, reporter ); if( reporter.HasMessage() ) { wxString msg = reporter.GetMessages(); std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_SIMULATION_MODEL ); //Remove \n and \r at e.o.l if any: msg.Trim(); ercItem->SetErrorMessage( msg ); ercItem->SetItems( symbol ); markers.emplace_back( new SCH_MARKER( ercItem, symbol->GetPosition() ) ); } } for( SCH_MARKER* marker : markers ) { sheet.LastScreen()->Append( marker ); err_count += 1; } } return err_count; } void ERC_TESTER::RunTests( DS_PROXY_VIEW_ITEM* aDrawingSheet, SCH_EDIT_FRAME* aEditFrame, KIFACE* aCvPcb, PROJECT* aProject, PROGRESS_REPORTER* aProgressReporter ) { m_sheetList.AnnotatePowerSymbols(); // Test duplicate sheet names inside a given sheet. While one can have multiple references // to the same file, each must have a unique name. if( m_settings.IsTestEnabled( ERCE_DUPLICATE_SHEET_NAME ) ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking sheet names..." ) ); TestDuplicateSheetNames( true ); } if( m_settings.IsTestEnabled( ERCE_BUS_ALIAS_CONFLICT ) ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking bus conflicts..." ) ); TestConflictingBusAliases(); } // The connection graph has a whole set of ERC checks it can run if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking conflicts..." ) ); // If we are using the new connectivity, make sure that we do a full-rebuild if( aEditFrame ) { if( ADVANCED_CFG::GetCfg().m_IncrementalConnectivity ) aEditFrame->RecalculateConnections( nullptr, GLOBAL_CLEANUP ); else aEditFrame->RecalculateConnections( nullptr, NO_CLEANUP ); } m_schematic->ConnectionGraph()->RunERC(); if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking units..." ) ); // Test is all units of each multiunit symbol have the same footprint assigned. if( m_settings.IsTestEnabled( ERCE_DIFFERENT_UNIT_FP ) ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking footprints..." ) ); TestMultiunitFootprints(); } if( m_settings.IsTestEnabled( ERCE_MISSING_UNIT ) || m_settings.IsTestEnabled( ERCE_MISSING_INPUT_PIN ) || m_settings.IsTestEnabled( ERCE_MISSING_POWER_INPUT_PIN ) || m_settings.IsTestEnabled( ERCE_MISSING_BIDI_PIN ) ) { TestMissingUnits(); } if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking pins..." ) ); if( m_settings.IsTestEnabled( ERCE_DIFFERENT_UNIT_NET ) ) TestMultUnitPinConflicts(); // Test pins on each net against the pin connection table if( m_settings.IsTestEnabled( ERCE_PIN_TO_PIN_ERROR ) || m_settings.IsTestEnabled( ERCE_POWERPIN_NOT_DRIVEN ) || m_settings.IsTestEnabled( ERCE_PIN_NOT_DRIVEN ) ) { TestPinToPin(); } // Test similar labels (i;e. labels which are identical when // using case insensitive comparisons) if( m_settings.IsTestEnabled( ERCE_SIMILAR_LABELS ) || m_settings.IsTestEnabled( ERCE_SIMILAR_POWER ) || m_settings.IsTestEnabled( ERCE_SIMILAR_LABEL_AND_POWER ) || m_settings.IsTestEnabled( ERCE_SAME_LOCAL_GLOBAL_LABEL ) ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking labels..." ) ); TestSimilarLabels(); TestSameLocalGlobalLabel(); } if( m_settings.IsTestEnabled( ERCE_UNRESOLVED_VARIABLE ) ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking for unresolved variables..." ) ); TestTextVars( aDrawingSheet ); } if( m_settings.IsTestEnabled( ERCE_SIMULATION_MODEL ) ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking SPICE models..." ) ); TestSimModelIssues(); } if( m_settings.IsTestEnabled( ERCE_NOCONNECT_CONNECTED ) ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking no connect pins for connections..." ) ); TestNoConnectPins(); } if( m_settings.IsTestEnabled( ERCE_LIB_SYMBOL_ISSUES ) || m_settings.IsTestEnabled( ERCE_LIB_SYMBOL_MISMATCH ) ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking for library symbol issues..." ) ); TestLibSymbolIssues(); } if( m_settings.IsTestEnabled( ERCE_FOOTPRINT_LINK_ISSUES ) && aCvPcb ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking for footprint link issues..." ) ); TestFootprintLinkIssues( aCvPcb, aProject ); } if( m_settings.IsTestEnabled( ERCE_FOOTPRINT_FILTERS ) ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking footprint assignments against footprint filters..." ) ); TestFootprintFilters(); } if( m_settings.IsTestEnabled( ERCE_ENDPOINT_OFF_GRID ) ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking for off grid pins and wires..." ) ); TestOffGridEndpoints(); } if( m_settings.IsTestEnabled( ERCE_FOUR_WAY_JUNCTION ) ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking for four way junctions..." ) ); TestFourWayJunction(); } if( m_settings.IsTestEnabled( ERCE_LABEL_MULTIPLE_WIRES ) ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking for labels on more than one wire..." ) ); TestLabelMultipleWires(); } if( m_settings.IsTestEnabled( ERCE_UNDEFINED_NETCLASS ) ) { if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking for undefined netclasses..." ) ); TestMissingNetclasses(); } m_schematic->ResolveERCExclusionsPostUpdate(); }