Files
kicad-source-mirror/pcbnew/drc/drc_test_provider_schematic_parity.cpp
John Beard c4c19018c3 Pcbnew: Restrict includes of pad.h/footprint.h
There are some headers which don't need to include pad.h (which brings
padstack.h) - they just need forward defines.

While pad.h is not an especially heavy include on the scale of things,
footprint is quite heavy. And both reduce inclusion load and
reduce the number of files needlessly sensitive to changes in headers.
2026-03-05 02:18:34 +08:00

388 lines
15 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers.
*
* 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 <board.h>
#include <footprint.h>
#include <pad.h>
#include <drc/drc_engine.h>
#include <drc/drc_item.h>
#include <drc/drc_rule.h>
#include <drc/drc_test_provider.h>
#include <kiway.h>
#include <netlist_reader/pcb_netlist.h>
/*
Schematic parity test.
Errors generated:
- DRCE_MISSING_FOOTPRINT
- DRCE_DUPLICATE_FOOTPRINT
- DRCE_EXTRA_FOOTPRINT
- DRCE_SCHEMATIC_PARITY
- DRCE_FOOTPRINT_FILTERS
TODO:
- cross-check PCB netlist against SCH netlist
- cross-check PCB fields against SCH fields
*/
class DRC_TEST_PROVIDER_SCHEMATIC_PARITY : public DRC_TEST_PROVIDER
{
public:
DRC_TEST_PROVIDER_SCHEMATIC_PARITY()
{
m_isRuleDriven = false;
}
virtual ~DRC_TEST_PROVIDER_SCHEMATIC_PARITY() = default;
virtual bool Run() override;
virtual const wxString GetName() const override { return wxT( "schematic_parity" ); };
private:
void testNetlist( NETLIST& aNetlist );
};
void DRC_TEST_PROVIDER_SCHEMATIC_PARITY::testNetlist( NETLIST& aNetlist )
{
BOARD* board = m_drcEngine->GetBoard();
auto compare = []( const FOOTPRINT* x, const FOOTPRINT* y )
{
return x->GetReference().CmpNoCase( y->GetReference() ) < 0;
};
auto footprints = std::set<FOOTPRINT*, decltype( compare )>( compare );
// Search for duplicate footprints on the board
for( FOOTPRINT* footprint : board->Footprints() )
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_DUPLICATE_FOOTPRINT ) )
break;
auto ins = footprints.insert( footprint );
if( !ins.second && !( footprint->GetAttributes() & FP_BOARD_ONLY ) )
{
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_DUPLICATE_FOOTPRINT );
drcItem->SetItems( footprint, *ins.first );
reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
}
}
// Search for component footprints in the netlist but not on the board.
for( unsigned ii = 0; ii < aNetlist.GetCount(); ii++ )
{
COMPONENT* component = aNetlist.GetComponent( ii );
FOOTPRINT* footprint = board->FindFootprintByReference( component->GetReference() );
if( footprint == nullptr )
{
if( !m_drcEngine->IsErrorLimitExceeded( DRCE_MISSING_FOOTPRINT ) )
{
wxString msg;
msg.Printf( _( "Missing footprint %s (%s)" ),
component->GetReference(),
component->GetValue() );
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_MISSING_FOOTPRINT );
drcItem->SetErrorMessage( msg );
reportViolation( drcItem, VECTOR2I(), UNDEFINED_LAYER );
}
}
else
{
if( component->GetValue() != footprint->GetValue()
&& !m_drcEngine->IsErrorLimitExceeded( DRCE_SCHEMATIC_PARITY ) )
{
wxString msg;
msg.Printf( _( "Value (%s) doesn't match symbol value (%s)" ),
footprint->GetReference(), footprint->GetValue(),
component->GetValue() );
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SCHEMATIC_PARITY );
drcItem->SetErrorMessage( msg );
drcItem->SetItems( footprint );
reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
}
if( component->GetFPID().GetUniStringLibId() != footprint->GetFPID().GetUniStringLibId()
&& !m_drcEngine->IsErrorLimitExceeded( DRCE_SCHEMATIC_PARITY ) )
{
wxString msg;
msg.Printf( _( "%s doesn't match footprint given by symbol (%s)" ),
footprint->GetFPID().GetUniStringLibId(),
component->GetFPID().GetUniStringLibId() );
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SCHEMATIC_PARITY );
drcItem->SetErrorMessage( msg );
drcItem->SetItems( footprint );
reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
}
if( !m_drcEngine->IsErrorLimitExceeded( DRCE_FOOTPRINT_FILTERS ) )
{
wxString libIdLower = footprint->GetFPID().GetUniStringLibId().Lower();
wxString fpNameLower = footprint->GetFPID().GetUniStringLibItemName().Lower();
size_t filtercount = component->GetFootprintFilters().GetCount();
bool found = ( 0 == filtercount ); // if no entries, do not filter
for( size_t jj = 0; jj < filtercount && !found; jj++ )
{
wxString filterLower = component->GetFootprintFilters()[jj].Lower();
if( filterLower.Find( ':' ) == wxNOT_FOUND )
found = fpNameLower.Matches( filterLower );
else
found = libIdLower.Matches( filterLower );
}
if( !found )
{
wxString msg;
msg.Printf( _( "%s doesn't match symbol's footprint filters (%s)" ),
footprint->GetFPID().GetUniStringLibId(),
wxJoin( component->GetFootprintFilters(), ' ' ) );
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_FOOTPRINT_FILTERS );
drcItem->SetErrorMessage( msg );
drcItem->SetItems( footprint );
reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
}
}
if( ( component->GetProperties().count( "dnp" ) > 0 )
!= ( ( footprint->GetAttributes() & FP_DNP ) > 0 )
&& !m_drcEngine->IsErrorLimitExceeded( DRCE_SCHEMATIC_PARITY ) )
{
wxString msg;
msg.Printf( _( "'%s' settings differ" ), _( "Do not populate" ) );
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SCHEMATIC_PARITY );
drcItem->SetErrorMessage( drcItem->GetErrorMessage( true ) + wxS( ": " ) + msg );
drcItem->SetItems( footprint );
reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
}
if( ( component->GetProperties().count( "exclude_from_bom" ) > 0 )
!= ( (footprint->GetAttributes() & FP_EXCLUDE_FROM_BOM ) > 0 )
&& !m_drcEngine->IsErrorLimitExceeded( DRCE_SCHEMATIC_PARITY ) )
{
wxString msg;
msg.Printf( _( "'%s' settings differ" ), _( "Exclude from bill of materials" ) );
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SCHEMATIC_PARITY );
drcItem->SetErrorMessage( drcItem->GetErrorMessage( true ) + wxS( ": " ) + msg );
drcItem->SetItems( footprint );
reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
}
// Compare custom fields between schematic component and PCB footprint
if( !m_drcEngine->IsErrorLimitExceeded( DRCE_SCHEMATIC_FIELDS_PARITY ) )
{
std::unordered_map<wxString, wxString> fpFieldsAsMap;
for( PCB_FIELD* field : footprint->GetFields() )
{
wxCHECK2( field, continue );
if( field->IsReference() || field->IsValue() || field->IsComponentClass() )
continue;
fpFieldsAsMap[field->GetName()] = field->GetText();
}
// Remove the extra component fields we don't want to evaluate here
nlohmann::ordered_map<wxString, wxString> compFields = component->GetFields();
compFields.erase( GetCanonicalFieldName( FIELD_T::REFERENCE ) );
compFields.erase( GetCanonicalFieldName( FIELD_T::VALUE ) );
compFields.erase( GetCanonicalFieldName( FIELD_T::FOOTPRINT ) );
compFields.erase( wxT( "Component Class" ) );
bool fieldsMatch = true;
wxString mismatchDetail;
for( const auto& [name, value] : compFields )
{
auto it = fpFieldsAsMap.find( name );
if( it == fpFieldsAsMap.end() )
{
fieldsMatch = false;
mismatchDetail = wxString::Format( _( "Missing symbol field '%s' in footprint" ), name );
break;
}
if( it->second != value )
{
fieldsMatch = false;
mismatchDetail = wxString::Format( _( "Field '%s' differs (PCB: '%s', Schematic: '%s')" ),
name, it->second, value );
break;
}
}
if( !fieldsMatch && !mismatchDetail.IsEmpty() )
{
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SCHEMATIC_FIELDS_PARITY );
drcItem->SetErrorMessage( mismatchDetail );
drcItem->SetItems( footprint );
reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
}
}
for( PAD* pad : footprint->Pads() )
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_NET_CONFLICT ) )
break;
if( !pad->CanHaveNumber() )
continue;
const COMPONENT_NET& sch_net = component->GetNet( pad->GetNumber() );
const wxString& pcb_netname = pad->GetNetname();
if( !pcb_netname.IsEmpty() && sch_net.GetPinName().IsEmpty() )
{
wxString msg;
msg.Printf( _( "No corresponding pin found in schematic" ) );
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT );
drcItem->SetErrorMessage( msg );
drcItem->SetItems( pad );
reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
}
else if( pcb_netname.IsEmpty() && !sch_net.GetNetName().IsEmpty() )
{
wxString msg;
msg.Printf( _( "Pad missing net given by schematic (%s)" ),
sch_net.GetNetName() );
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT );
drcItem->SetErrorMessage( msg );
drcItem->SetItems( pad );
reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
}
else if( pcb_netname != sch_net.GetNetName()
&& !( pcb_netname.starts_with(
wxT( "unconnected-" ) )
&& pcb_netname.starts_with( sch_net.GetNetName() ) ) )
{
wxString msg;
msg.Printf( _( "Pad net (%s) doesn't match net given by schematic (%s)" ),
pcb_netname,
sch_net.GetNetName() );
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT );
drcItem->SetErrorMessage( msg );
drcItem->SetItems( pad );
reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
}
}
for( unsigned jj = 0; jj < component->GetNetCount(); ++jj )
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_NET_CONFLICT ) )
break;
const COMPONENT_NET& sch_net = component->GetNet( jj );
if( !footprint->FindPadByNumber( sch_net.GetPinName() ) )
{
wxString msg;
if( sch_net.GetNetName().StartsWith( wxT( "unconnected-" ) ) )
{
msg = sch_net.GetPinName();
}
else
{
msg = wxString::Format( wxT( "%s (%s)" ),
sch_net.GetPinName(),
sch_net.GetNetName() );
}
msg.Printf( _( "No pad found for pin %s in schematic" ), msg );
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_NET_CONFLICT );
drcItem->SetErrorMessage( msg );
drcItem->SetItems( footprint );
reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
}
}
}
}
// Search for component footprints found on board but not in netlist.
for( FOOTPRINT* footprint : board->Footprints() )
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_EXTRA_FOOTPRINT ) )
break;
if( footprint->GetAttributes() & FP_BOARD_ONLY )
continue;
if( !aNetlist.GetComponentByReference( footprint->GetReference() ) )
{
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_EXTRA_FOOTPRINT );
drcItem->SetItems( footprint );
reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
}
}
}
bool DRC_TEST_PROVIDER_SCHEMATIC_PARITY::Run()
{
if( m_drcEngine->GetTestFootprints() )
{
if( !reportPhase( _( "Checking PCB to schematic parity..." ) ) )
return false;
auto netlist = m_drcEngine->GetSchematicNetlist();
if( !netlist )
{
REPORT_AUX( wxT( "No netlist provided, skipping schematic parity tests." ) );
return true;
}
testNetlist( *netlist );
}
return !m_drcEngine->IsCancelled();
}
namespace detail
{
static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_SCHEMATIC_PARITY> dummy;
}