From 262f1fdabb34009edb20e8127a890cdb2bf9e182 Mon Sep 17 00:00:00 2001 From: John Beard Date: Wed, 27 Aug 2025 22:43:02 +0800 Subject: [PATCH] Pcbnew: add layer mode/stackup checks to library parity DRC --- common/CMakeLists.txt | 1 + .../dialog_footprint_properties_fp_editor.cpp | 16 +--- .../drc/drc_test_provider_library_parity.cpp | 78 +++++++++++++++++++ pcbnew/layer_utils.cpp | 40 ++++++++++ pcbnew/layer_utils.h | 61 +++++++++++++++ 5 files changed, 182 insertions(+), 14 deletions(-) create mode 100644 pcbnew/layer_utils.cpp create mode 100644 pcbnew/layer_utils.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index c44f6a0fcc..d51c1019b1 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -877,6 +877,7 @@ set( PCB_COMMON_SRCS ${CMAKE_SOURCE_DIR}/pcbnew/pcb_marker.cpp ${CMAKE_SOURCE_DIR}/pcbnew/footprint.cpp ${CMAKE_SOURCE_DIR}/pcbnew/fix_board_shape.cpp + ${CMAKE_SOURCE_DIR}/pcbnew/layer_utils.cpp ${CMAKE_SOURCE_DIR}/pcbnew/netinfo_item.cpp ${CMAKE_SOURCE_DIR}/pcbnew/netinfo_list.cpp ${CMAKE_SOURCE_DIR}/pcbnew/pad.cpp diff --git a/pcbnew/dialogs/dialog_footprint_properties_fp_editor.cpp b/pcbnew/dialogs/dialog_footprint_properties_fp_editor.cpp index 226b032520..4e98257698 100644 --- a/pcbnew/dialogs/dialog_footprint_properties_fp_editor.cpp +++ b/pcbnew/dialogs/dialog_footprint_properties_fp_editor.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -575,19 +576,6 @@ static LSET GetAllUsedFootprintLayers( const FOOTPRINT& aFootprint ) } -static wxString GetLayerStringList( const BOARD& aBoard, const LSET& layers ) -{ - std::vector layerNames; - - for( PCB_LAYER_ID layer : layers.UIOrder() ) - { - layerNames.push_back( aBoard.GetLayerName( layer ) ); - } - - return AccumulateDescriptions( layerNames ); -} - - bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::Validate() { if( !m_itemsGrid->CommitPendingChanges() ) @@ -692,7 +680,7 @@ bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::Validate() m_delayedErrorMessage = wxString::Format( _( "You are trying to remove layers that are used by the footprint: %s.\n" "Please remove the objects that use these layers first." ), - GetLayerStringList( *m_frame->GetBoard(), usedLayers ) ); + LAYER_UTILS::AccumulateNames( usedLayers, m_frame->GetBoard() ) ); m_delayedFocusGrid = m_customUserLayersGrid; m_delayedFocusColumn = 0; m_delayedFocusRow = 0; diff --git a/pcbnew/drc/drc_test_provider_library_parity.cpp b/pcbnew/drc/drc_test_provider_library_parity.cpp index 29fa53da60..21c24961b4 100644 --- a/pcbnew/drc/drc_test_provider_library_parity.cpp +++ b/pcbnew/drc/drc_test_provider_library_parity.cpp @@ -22,6 +22,7 @@ */ #include +#include #include #include #include @@ -581,6 +582,74 @@ bool zoneNeedsUpdate( const ZONE* a, const ZONE* b, REPORTER* aReporter ) } +/** + * Compare the stackup related settings of two footprints. + * + * Returns true if they differ. + */ +bool stackupNeedsUpdate( const FOOTPRINT& a, const FOOTPRINT& b, REPORTER* aReporter ) +{ + bool diff = false; + + TEST( a.GetStackupMode(), b.GetStackupMode(), + wxString::Format( _( "Footprint stackup mode differs." ) ) ); + + const LSET& aLayers = a.GetLayerSet(); + const LSET& bLayers = b.GetLayerSet(); + + TEST( aLayers, bLayers, + wxString::Format( _( "Footprint layers differ." ) ) ); + + return diff; +} + + +/** + * Report board->footprint stackup differences. + * + * This is not necessarily a comparison failure, but may be useful information + * for the user to see. + * + * @return true if there is + */ +bool footprintVsBoardStackup( const FOOTPRINT& aFp, const BOARD& aBoard, REPORTER* aReporter ) +{ + if( aFp.GetStackupMode() == FOOTPRINT_STACKUP::EXPAND_INNER_LAYERS ) + return false; + + // Filter only layers that can differ between footprint and board + const LSET& fpLayers = aFp.GetStackupLayers(); + const LSET& brdLayers = aBoard.GetEnabledLayers() & ( LSET::AllCuMask() | LSET::UserDefinedLayersMask() ); + + bool mismatch = false; + + // Any layer in the FP and not on the board is flagged + const LSET onlyInFp = fpLayers & ~brdLayers; + + if( onlyInFp.count() ) + { + mismatch = true; + if( aReporter ) + aReporter->Report( wxString::Format( _( "Footprint has %lu layers not on board: %s" ), onlyInFp.count(), + LAYER_UTILS::AccumulateNames( onlyInFp, &aBoard ) ) ); + } + + // Only look at copper layers here: user layers on the board and not in the FP is normal + const LSET cuOnlyInBoard = ( brdLayers & ~fpLayers ) & LSET::AllCuMask(); + + if( cuOnlyInBoard.count() ) + { + mismatch = true; + if( aReporter ) + aReporter->Report( wxString::Format( _( "Board has %lu copper layers not in footprint: %s" ), + cuOnlyInBoard.count(), + LAYER_UTILS::AccumulateNames( cuOnlyInBoard, &aBoard ) ) ); + } + + return mismatch; +} + + bool FOOTPRINT::FootprintNeedsUpdate( const FOOTPRINT* aLibFP, int aCompareFlags, REPORTER* aReporter ) { @@ -617,6 +686,9 @@ bool FOOTPRINT::FootprintNeedsUpdate( const FOOTPRINT* aLibFP, int aCompareFlags aLibFP = temp.get(); + // These checks don't set off errors, they're just informational + footprintVsBoardStackup( *this, *GetBoard(), aReporter ); + #define TEST_ATTR( a, b, attr, msg ) TEST( ( a & attr ), ( b & attr ), msg ) TEST_ATTR( GetAttributes(), aLibFP->GetAttributes(), (FP_THROUGH_HOLE | FP_SMD), @@ -649,6 +721,12 @@ bool FOOTPRINT::FootprintNeedsUpdate( const FOOTPRINT* aLibFP, int aCompareFlags #define REPORT( msg ) { if( aReporter ) aReporter->Report( msg ); } #define CHECKPOINT { if( diff && !aReporter ) return diff; } + if( stackupNeedsUpdate( *this, *aLibFP, aReporter ) ) + { + diff = true; + REPORT( _( "Footprint stackup differs." ) ); + } + // Clearance and zone connection overrides are as likely to be set at the board level as in // the library. // diff --git a/pcbnew/layer_utils.cpp b/pcbnew/layer_utils.cpp new file mode 100644 index 0000000000..dacf1970e9 --- /dev/null +++ b/pcbnew/layer_utils.cpp @@ -0,0 +1,40 @@ +/* + * 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 "layer_utils.h" + + +wxString LAYER_UTILS::AccumulateNames( const LSEQ& aLayers, const BOARD* aBoard ) +{ + wxString result; + + for( const PCB_LAYER_ID layer : aLayers ) + { + if( !result.IsEmpty() ) + result += ", "; + + result += aBoard ? aBoard->GetLayerName( layer ) : LayerName( layer ); + } + + return result; +} diff --git a/pcbnew/layer_utils.h b/pcbnew/layer_utils.h new file mode 100644 index 0000000000..36d3a3a7c9 --- /dev/null +++ b/pcbnew/layer_utils.h @@ -0,0 +1,61 @@ +/* + * 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 + */ + +#pragma once + +#include + +#include +#include +#include + + +/** + * Utility functions for dealing with layers in the context of a PCB board.s + * + * This includes functions that need access to the board to get layer names, + * and other more complex, but reusable operations that either shouldn't be in LSET/LSEQ's + * interface or need access to Pcbnew types. + */ +namespace LAYER_UTILS +{ + +/** + * Accumulate layer names from a layer set into a comma separated string. + * + * @param aLayers is the list of layers to accumulate. + * @param aBoard is the board to get layer names from, if null the default names + * are used. + */ +wxString AccumulateNames( const LSEQ& aLayers, const BOARD* aBoard ); + +/** + * Accumulate layer names from a layer set into a comma separated string, + * in UI order. + */ +inline wxString AccumulateNames( const LSET& aLayers, const BOARD* aBoard ) +{ + return AccumulateNames( aLayers.UIOrder(), aBoard ); +} + +} // namespace LAYER_UTILS