7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-18 19:59:18 +00:00

Implement jumpers for footprints

Fixes https://gitlab.com/kicad/code/kicad/-/issues/2558
This commit is contained in:
Jon Evans 2025-03-22 22:26:07 -04:00
parent 805ca0c791
commit 1c83f0a70b
23 changed files with 2693 additions and 187 deletions

View File

@ -909,6 +909,19 @@ message Footprint3DModel
double opacity = 6;
}
message JumperGroup
{
repeated string pad_names = 1;
}
message JumperSettings
{
/// If true, duplicate pad names in this footprint are jumpered together
bool duplicate_names_are_jumpered = 1;
repeated JumperGroup groups = 2;
}
// A footprint definition (i.e. what would be in a library)
message Footprint
{
@ -926,6 +939,8 @@ message Footprint
// All footprint items except for mandatory fields
repeated google.protobuf.Any items = 11;
JumperSettings jumpers = 12;
}
// An instance of a footprint on a board

View File

@ -8,12 +8,15 @@ date
description
design
docs
duplicate_pin_numbers_are_jumpers
export
field
fields
footprint
footprints
fp
group
jumper_pin_groups
lib
libpart
libparts

View File

@ -106,6 +106,7 @@ dimension_units
dnp
drawings
drill
duplicate_pad_numbers_are_jumpers
edge
edge_clearance
edge_cuts_line_width
@ -189,6 +190,7 @@ island
island_removal_mode
island_area_min
italic
jumper_pad_groups
justify
keepout
keep_end_layers

View File

@ -411,6 +411,25 @@ XNODE* NETLIST_EXPORTER_XML::makeSymbols( unsigned aCtl )
xproperty->AddAttribute( wxT( "name" ), wxT( "ki_fp_filters" ) );
xproperty->AddAttribute( wxT( "value" ), filters.Trim( false ) );
}
if( part->GetDuplicatePinNumbersAreJumpers() )
xcomp->AddChild( node( wxT( "duplicate_pin_numbers_are_jumpers" ), wxT( "1" ) ) );
const std::vector<std::set<wxString>>& jumperGroups = part->JumperPinGroups();
if( !jumperGroups.empty() )
{
XNODE* groupNode;
xcomp->AddChild( xproperty = node( wxT( "jumper_pin_groups" ) ) );
for( const std::set<wxString>& group : jumperGroups )
{
xproperty->AddChild( groupNode = node( wxT( "group" ) ) );
for( const wxString& padName : group )
groupNode->AddAttribute( wxT( "pin" ), padName );
}
}
}
XNODE* xsheetpath;

View File

@ -635,6 +635,7 @@ void CN_CONNECTIVITY_ALGO::propagateConnections( BOARD_COMMIT* aCommit )
void CN_CONNECTIVITY_ALGO::PropagateNets( BOARD_COMMIT* aCommit )
{
updateJumperPads();
m_connClusters = SearchClusters( CSM_PROPAGATE );
propagateConnections( aCommit );
}
@ -936,3 +937,57 @@ void CN_CONNECTIVITY_ALGO::SetProgressReporter( PROGRESS_REPORTER* aReporter )
{
m_progressReporter = aReporter;
}
void CN_CONNECTIVITY_ALGO::updateJumperPads()
{
// Map of footprint -> map of pad number -> list of CN_ITEMs for pads with that number
std::map<FOOTPRINT*, std::map<wxString, std::vector<CN_ITEM*>>> padsByFootprint;
for( CN_ITEM* item : m_itemList )
{
if( item->Parent()->Type() != PCB_PAD_T )
continue;
auto pad = static_cast<const PAD*>( item->Parent() );
FOOTPRINT* fp = pad->GetParentFootprint();
padsByFootprint[fp][ pad->GetNumber() ].emplace_back( item );
}
for( auto& [footprint, padsMap] : padsByFootprint )
{
if( footprint->GetDuplicatePadNumbersAreJumpers() )
{
for( const std::vector<CN_ITEM*>& padsList : padsMap | std::views::values )
{
for( size_t i = 0; i < padsList.size(); ++i )
{
for( size_t j = 1; j < padsList.size(); ++j )
{
padsList[i]->Connect( padsList[j] );
padsList[j]->Connect( padsList[i] );
}
}
}
}
for( const std::set<wxString>& group : footprint->JumperPadGroups() )
{
std::vector<CN_ITEM*> toConnect;
for( const wxString& padNumber : group )
std::ranges::copy( padsMap[padNumber], std::back_inserter( toConnect ) );
for( size_t i = 0; i < toConnect.size(); ++i )
{
for( size_t j = 1; j < toConnect.size(); ++j )
{
toConnect[i]->Connect( toConnect[j] );
toConnect[j]->Connect( toConnect[i] );
}
}
}
}
}

View File

@ -285,6 +285,8 @@ private:
void markItemNetAsDirty( const BOARD_ITEM* aItem );
void updateJumperPads();
private:
CONNECTIVITY_DATA* m_parentConnectivityData;
CN_LIST m_itemList;

View File

@ -228,6 +228,9 @@ DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR(
m_bpAddPadGroup->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
m_bpRemovePadGroup->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
m_btnCreateJumperPadGroup->SetBitmap( KiBitmapBundle( BITMAPS::right ) );
m_btnRemoveJumperPadGroup->SetBitmap( KiBitmapBundle( BITMAPS::left ) );
SetupStandardButtons();
finishDialogSettings();
@ -351,6 +354,36 @@ bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::TransferDataToWindow()
}
}
m_cbDuplicatePadsAreJumpers->SetValue( m_footprint->GetDuplicatePadNumbersAreJumpers() );
m_btnCreateJumperPadGroup->Disable();
m_btnRemoveJumperPadGroup->Disable();
// Pad connections tab
std::set<wxString> availablePads;
for( const PAD* pad : m_footprint->Pads() )
availablePads.insert( pad->GetNumber() );
for( const std::set<wxString>& group : m_footprint->JumperPadGroups() )
{
wxString groupTxt;
size_t i = 0;
for( const wxString& pinNumber : group )
{
availablePads.erase( pinNumber );
groupTxt << pinNumber;
if( ++i < group.size() )
groupTxt << ", ";
}
m_listJumperPadGroups->Append( groupTxt );
}
for( const wxString& pin : availablePads )
m_listAvailablePads->AppendString( pin );
// Items grid
for( int col = 0; col < m_itemsGrid->GetNumberCols(); col++ )
{
@ -677,6 +710,23 @@ bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::TransferDataFromWindow()
m_footprint->AddNetTiePadGroup( group );
}
m_footprint->SetDuplicatePadNumbersAreJumpers( m_cbDuplicatePadsAreJumpers->GetValue() );
std::vector<std::set<wxString>>& jumpers = m_footprint->JumperPadGroups();
jumpers.clear();
for( unsigned i = 0; i < m_listJumperPadGroups->GetCount(); ++i )
{
wxStringTokenizer tokenizer( m_listJumperPadGroups->GetString( i ), ", " );
std::set<wxString>& group = jumpers.emplace_back();
while( tokenizer.HasMoreTokens() )
{
if( wxString token = tokenizer.GetNextToken(); !token.IsEmpty() )
group.insert( token );
}
}
// Copy the models from the panel to the footprint
std::vector<FP_3DMODEL>& panelList = m_3dPanel->GetModelList();
std::vector<FP_3DMODEL>* fpList = &m_footprint->Models();
@ -1019,3 +1069,77 @@ void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::OnChoice( wxCommandEvent& event )
if( m_initialized )
OnModify();
}
void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::OnBtnCreateJumperPadGroup( wxCommandEvent& aEvent )
{
wxArrayInt selections;
int n = m_listAvailablePads->GetSelections( selections );
wxCHECK( n > 0, /* void */ );
m_listJumperPadGroups->Freeze();
m_listAvailablePads->Freeze();
wxString group;
int i = 0;
for( int idx : selections )
{
group << m_listAvailablePads->GetString( idx );
if( ++i < n )
group << ", ";
}
for( int idx = selections.size() - 1; idx >= 0; --idx )
m_listAvailablePads->Delete( selections[idx] );
m_listJumperPadGroups->AppendString( group );
m_listJumperPadGroups->Thaw();
m_listAvailablePads->Thaw();
}
void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::OnBtnRemoveJumperPadGroup( wxCommandEvent& aEvent )
{
wxArrayInt selections;
int n = m_listJumperPadGroups->GetSelections( selections );
wxCHECK( n > 0, /* void */ );
m_listJumperPadGroups->Freeze();
m_listAvailablePads->Freeze();
for( int idx : selections )
{
wxStringTokenizer tokenizer( m_listJumperPadGroups->GetString( idx ), ", " );
while( tokenizer.HasMoreTokens() )
{
if( wxString token = tokenizer.GetNextToken(); !token.IsEmpty() )
m_listAvailablePads->AppendString( token );
}
}
for( int idx = selections.size() - 1; idx >= 0; --idx )
m_listJumperPadGroups->Delete( selections[idx] );
m_listJumperPadGroups->Thaw();
m_listAvailablePads->Thaw();
}
void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::OnGroupedPadListClick( wxCommandEvent& aEvent )
{
wxArrayInt selections;
int n = m_listJumperPadGroups->GetSelections( selections );
m_btnRemoveJumperPadGroup->Enable( n > 0 );
}
void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::OnAvailablePadsClick( wxCommandEvent& aEvent )
{
wxArrayInt selections;
int n = m_listJumperPadGroups->GetSelections( selections );
m_btnCreateJumperPadGroup->Enable( n > 0 );
}

View File

@ -95,6 +95,10 @@ private:
void OnText( wxCommandEvent& event ) override;
void OnChoice( wxCommandEvent& event ) override;
void OnCheckBox( wxCommandEvent& event ) override;
void OnBtnCreateJumperPadGroup( wxCommandEvent& event ) override;
void OnBtnRemoveJumperPadGroup( wxCommandEvent& event ) override;
void OnGroupedPadListClick( wxCommandEvent& event ) override;
void OnAvailablePadsClick( wxCommandEvent& event ) override;
bool checkFootprintName( const wxString& aFootprintName, LIB_ID* doOverwrite );

View File

@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version 4.0.0-0-g0efcecf)
// C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
@ -226,7 +226,7 @@ DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::DIALOG_FOOTPRINT_PROPERTIES_FP_EDITO
m_PanelGeneral->SetSizer( m_PanelPropertiesBoxSizer );
m_PanelGeneral->Layout();
m_PanelPropertiesBoxSizer->Fit( m_PanelGeneral );
m_NoteBook->AddPage( m_PanelGeneral, _("General"), true );
m_NoteBook->AddPage( m_PanelGeneral, _("General"), false );
m_PanelClearances = new wxPanel( m_NoteBook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizerPanelClearances;
bSizerPanelClearances = new wxBoxSizer( wxVERTICAL );
@ -397,6 +397,78 @@ DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::DIALOG_FOOTPRINT_PROPERTIES_FP_EDITO
m_PanelClearances->Layout();
bSizerPanelClearances->Fit( m_PanelClearances );
m_NoteBook->AddPage( m_PanelClearances, _("Clearance Overrides and Settings"), false );
m_PanelPinConnections = new wxPanel( m_NoteBook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizer19;
bSizer19 = new wxBoxSizer( wxVERTICAL );
m_cbDuplicatePadsAreJumpers = new wxCheckBox( m_PanelPinConnections, wxID_ANY, _("Pads with duplicate numbers are jumpers"), wxDefaultPosition, wxDefaultSize, 0 );
m_cbDuplicatePadsAreJumpers->SetToolTip( _("When enabled, this footprint can have more than one pad with the same number, and pads with the same number will be considered to be jumpered together internally.") );
bSizer19->Add( m_cbDuplicatePadsAreJumpers, 0, wxALL, 5 );
wxStaticBoxSizer* sbJumperPinGroups;
sbJumperPinGroups = new wxStaticBoxSizer( new wxStaticBox( m_PanelPinConnections, wxID_ANY, _("Jumper Pad Groups") ), wxVERTICAL );
wxBoxSizer* bSizer20;
bSizer20 = new wxBoxSizer( wxHORIZONTAL );
wxBoxSizer* bSizer22;
bSizer22 = new wxBoxSizer( wxVERTICAL );
stLabelAvailablePads = new wxStaticText( sbJumperPinGroups->GetStaticBox(), wxID_ANY, _("Available pads"), wxDefaultPosition, wxDefaultSize, 0 );
stLabelAvailablePads->Wrap( -1 );
bSizer22->Add( stLabelAvailablePads, 0, wxALL, 5 );
m_listAvailablePads = new wxListBox( sbJumperPinGroups->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_EXTENDED|wxLB_SORT );
m_listAvailablePads->SetMinSize( wxSize( 200,-1 ) );
bSizer22->Add( m_listAvailablePads, 1, wxALL|wxEXPAND, 5 );
bSizer20->Add( bSizer22, 1, wxEXPAND, 5 );
wxBoxSizer* bSizer21;
bSizer21 = new wxBoxSizer( wxVERTICAL );
m_btnCreateJumperPadGroup = new wxBitmapButton( sbJumperPinGroups->GetStaticBox(), wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 );
m_btnCreateJumperPadGroup->SetToolTip( _("Create jumper group from the selected pads") );
bSizer21->Add( m_btnCreateJumperPadGroup, 0, wxALL, 5 );
m_btnRemoveJumperPadGroup = new wxBitmapButton( sbJumperPinGroups->GetStaticBox(), wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 );
m_btnRemoveJumperPadGroup->SetToolTip( _("Remove the selected jumper pad group") );
bSizer21->Add( m_btnRemoveJumperPadGroup, 0, wxALL, 5 );
bSizer20->Add( bSizer21, 0, wxALIGN_CENTER_VERTICAL, 5 );
wxBoxSizer* bSizer23;
bSizer23 = new wxBoxSizer( wxVERTICAL );
stLabelGroups = new wxStaticText( sbJumperPinGroups->GetStaticBox(), wxID_ANY, _("Grouped pads"), wxDefaultPosition, wxDefaultSize, 0 );
stLabelGroups->Wrap( -1 );
bSizer23->Add( stLabelGroups, 0, wxALL, 5 );
m_listJumperPadGroups = new wxListBox( sbJumperPinGroups->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_EXTENDED|wxLB_SORT );
m_listJumperPadGroups->SetMinSize( wxSize( 200,-1 ) );
bSizer23->Add( m_listJumperPadGroups, 1, wxALL|wxEXPAND, 5 );
bSizer20->Add( bSizer23, 1, wxEXPAND, 5 );
sbJumperPinGroups->Add( bSizer20, 1, wxEXPAND, 5 );
bSizer19->Add( sbJumperPinGroups, 1, wxALL|wxTOP, 5 );
m_PanelPinConnections->SetSizer( bSizer19 );
m_PanelPinConnections->Layout();
bSizer19->Fit( m_PanelPinConnections );
m_NoteBook->AddPage( m_PanelPinConnections, _("Pad Connections"), true );
m_GeneralBoxSizer->Add( m_NoteBook, 1, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 10 );
@ -442,6 +514,10 @@ DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::DIALOG_FOOTPRINT_PROPERTIES_FP_EDITO
m_padGroupsGrid->Connect( wxEVT_SIZE, wxSizeEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnGridSize ), NULL, this );
m_bpAddPadGroup->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnAddPadGroup ), NULL, this );
m_bpRemovePadGroup->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnRemovePadGroup ), NULL, this );
m_listAvailablePads->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnAvailablePadsClick ), NULL, this );
m_btnCreateJumperPadGroup->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnBtnCreateJumperPadGroup ), NULL, this );
m_btnRemoveJumperPadGroup->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnBtnRemoveJumperPadGroup ), NULL, this );
m_listJumperPadGroups->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnGroupedPadListClick ), NULL, this );
}
DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::~DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE()
@ -468,5 +544,9 @@ DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::~DIALOG_FOOTPRINT_PROPERTIES_FP_EDIT
m_padGroupsGrid->Disconnect( wxEVT_SIZE, wxSizeEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnGridSize ), NULL, this );
m_bpAddPadGroup->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnAddPadGroup ), NULL, this );
m_bpRemovePadGroup->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnRemovePadGroup ), NULL, this );
m_listAvailablePads->Disconnect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnAvailablePadsClick ), NULL, this );
m_btnCreateJumperPadGroup->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnBtnCreateJumperPadGroup ), NULL, this );
m_btnRemoveJumperPadGroup->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnBtnRemoveJumperPadGroup ), NULL, this );
m_listJumperPadGroups->Disconnect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::OnGroupedPadListClick ), NULL, this );
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version 4.0.0-0-g0efcecf)
// C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
@ -34,12 +34,13 @@ class WX_GRID;
#include <wx/checkbox.h>
#include <wx/panel.h>
#include <wx/gbsizer.h>
#include <wx/listbox.h>
#include <wx/notebook.h>
#include <wx/dialog.h>
///////////////////////////////////////////////////////////////////////////
#define ID_NOTEBOOK 1000
#define ID_NOTEBOOK 6000
///////////////////////////////////////////////////////////////////////////////
/// Class DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE
@ -92,6 +93,14 @@ class DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE : public DIALOG_SHIM
WX_GRID* m_padGroupsGrid;
STD_BITMAP_BUTTON* m_bpAddPadGroup;
STD_BITMAP_BUTTON* m_bpRemovePadGroup;
wxPanel* m_PanelPinConnections;
wxCheckBox* m_cbDuplicatePadsAreJumpers;
wxStaticText* stLabelAvailablePads;
wxListBox* m_listAvailablePads;
wxBitmapButton* m_btnCreateJumperPadGroup;
wxBitmapButton* m_btnRemoveJumperPadGroup;
wxStaticText* stLabelGroups;
wxListBox* m_listJumperPadGroups;
wxStdDialogButtonSizer* m_sdbSizerStdButtons;
wxButton* m_sdbSizerStdButtonsOK;
wxButton* m_sdbSizerStdButtonsCancel;
@ -110,6 +119,10 @@ class DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE : public DIALOG_SHIM
virtual void OnCheckBox( wxCommandEvent& event ) { event.Skip(); }
virtual void OnAddPadGroup( wxCommandEvent& event ) { event.Skip(); }
virtual void OnRemovePadGroup( wxCommandEvent& event ) { event.Skip(); }
virtual void OnAvailablePadsClick( wxCommandEvent& event ) { event.Skip(); }
virtual void OnBtnCreateJumperPadGroup( wxCommandEvent& event ) { event.Skip(); }
virtual void OnBtnRemoveJumperPadGroup( wxCommandEvent& event ) { event.Skip(); }
virtual void OnGroupedPadListClick( wxCommandEvent& event ) { event.Skip(); }
public:

View File

@ -131,6 +131,10 @@ FOOTPRINT::FOOTPRINT( const FOOTPRINT& aFootprint ) :
m_zoneConnection = aFootprint.m_zoneConnection;
m_netTiePadGroups = aFootprint.m_netTiePadGroups;
m_fileFormatVersionAtLoad = aFootprint.m_fileFormatVersionAtLoad;
m_duplicatePadNumbersAreJumpers = aFootprint.m_duplicatePadNumbersAreJumpers;
std::ranges::copy( aFootprint.m_jumperPadGroups,
std::inserter( m_jumperPadGroups, m_jumperPadGroups.end() ) );
std::map<BOARD_ITEM*, BOARD_ITEM*> ptrMap;
@ -751,6 +755,10 @@ FOOTPRINT& FOOTPRINT::operator=( FOOTPRINT&& aOther )
m_solderPasteMarginRatio = aOther.m_solderPasteMarginRatio;
m_zoneConnection = aOther.m_zoneConnection;
m_netTiePadGroups = aOther.m_netTiePadGroups;
m_duplicatePadNumbersAreJumpers = aOther.m_duplicatePadNumbersAreJumpers;
std::ranges::copy( aOther.m_jumperPadGroups,
std::inserter( m_jumperPadGroups, m_jumperPadGroups.end() ) );
// Move the fields
for( PCB_FIELD* field : m_fields )
@ -2722,6 +2730,18 @@ wxString FOOTPRINT::GetNextPadNumber( const wxString& aLastPadNumber ) const
}
std::optional<const std::set<wxString>> FOOTPRINT::GetJumperPadGroup( const wxString& aPadNumber ) const
{
for( const std::set<wxString>& group : m_jumperPadGroups )
{
if( group.contains( aPadNumber ) )
return group;
}
return std::nullopt;
}
void FOOTPRINT::AutoPositionFields()
{
// Auto-position reference and value

View File

@ -810,6 +810,19 @@ public:
*/
wxString GetNextPadNumber( const wxString& aLastPadName ) const;
bool GetDuplicatePadNumbersAreJumpers() const { return m_duplicatePadNumbersAreJumpers; }
void SetDuplicatePadNumbersAreJumpers( bool aEnabled ) { m_duplicatePadNumbersAreJumpers = aEnabled; }
/**
* Each jumper pad group is a set of pad numbers that should be treated as internally connected.
* @return The list of jumper pad groups in this footprint
*/
std::vector<std::set<wxString>>& JumperPadGroups() { return m_jumperPadGroups; }
const std::vector<std::set<wxString>>& JumperPadGroups() const { return m_jumperPadGroups; }
/// Retrieves the jumper group containing the specified pad number, if one exists
std::optional<const std::set<wxString>> GetJumperPadGroup( const wxString& aPadNumber ) const;
/**
* Position Reference and Value fields at the top and bottom of footprint's bounding box.
*/
@ -1109,6 +1122,14 @@ private:
wxArrayString* m_initial_comments; // s-expression comments in the footprint,
// lazily allocated only if needed for speed
/// A list of jumper pad groups, each of which is a set of pad numbers that should be jumpered
/// together (treated as internally connected for the purposes of connectivity)
std::vector<std::set<wxString>> m_jumperPadGroups;
/// Flag that this footprint should automatically treat sets of two or more pads with the same
/// number as jumpered pin groups
bool m_duplicatePadNumbersAreJumpers;
SHAPE_POLY_SET m_courtyard_cache_front; // Note that a footprint can have both front and back
SHAPE_POLY_SET m_courtyard_cache_back; // courtyards populated.
mutable HASH_128 m_courtyard_cache_front_hash;

View File

@ -738,6 +738,60 @@ bool BOARD_NETLIST_UPDATER::updateFootprintParameters( FOOTPRINT* aPcbFootprint,
m_reporter->Report( msg, RPT_SEVERITY_ACTION );
}
if( aNetlistComponent->GetDuplicatePadNumbersAreJumpers()
!= aPcbFootprint->GetDuplicatePadNumbersAreJumpers() )
{
bool value = aNetlistComponent->GetDuplicatePadNumbersAreJumpers();
if( !m_isDryRun )
{
changed = true;
aPcbFootprint->SetDuplicatePadNumbersAreJumpers( value );
if( value )
{
msg.Printf( _( "Added %s 'duplicate pad numbers are jumpers' attribute." ),
aPcbFootprint->GetReference() );
}
else
{
msg.Printf( _( "Removed %s 'duplicate pad numbers are jumpers' attribute." ),
aPcbFootprint->GetReference() );
}
}
else
{
if( value )
{
msg.Printf( _( "Add %s 'duplicate pad numbers are jumpers' attribute." ),
aPcbFootprint->GetReference() );
}
else
{
msg.Printf( _( "Remove %s 'duplicate pad numbers are jumpers' attribute." ),
aPcbFootprint->GetReference() );
}
}
m_reporter->Report( msg, RPT_SEVERITY_ACTION );
}
if( aNetlistComponent->JumperPadGroups() != aPcbFootprint->JumperPadGroups() )
{
if( !m_isDryRun )
{
changed = true;
aPcbFootprint->JumperPadGroups() = aNetlistComponent->JumperPadGroups();
msg.Printf( _( "Updated %s jumper pad groups" ), aPcbFootprint->GetReference() );
}
else
{
msg.Printf( _( "Update %s jumper pad groups" ), aPcbFootprint->GetReference() );
}
m_reporter->Report( msg, RPT_SEVERITY_ACTION );
}
if( changed && copy )
m_commit.Modified( aPcbFootprint, copy );
else if( copy )

View File

@ -309,6 +309,9 @@ void KICAD_NETLIST_PARSER::parseComponent()
nlohmann::ordered_map<wxString, wxString> fields;
std::unordered_set<wxString> componentClasses;
bool duplicatePinsAreJumpers = false;
std::vector<std::set<wxString>> jumperPinGroups;
// The token comp was read, so the next data is (ref P1)
while( (token = NextTok() ) != T_RIGHT )
{
@ -487,6 +490,51 @@ void KICAD_NETLIST_PARSER::parseComponent()
break;
case T_duplicate_pin_numbers_are_jumpers:
{
NeedSYMBOLorNUMBER();
duplicatePinsAreJumpers = From_UTF8( CurText() ) == wxT( "1" );
NeedRIGHT();
break;
}
case T_jumper_pin_groups:
{
std::set<wxString>* currentGroup = nullptr;
for( token = NextTok(); currentGroup || token != T_RIGHT; token = NextTok() )
{
if( token == T_LEFT )
token = NextTok();
switch( token )
{
case T_group:
currentGroup = &jumperPinGroups.emplace_back();
break;
case T_pin:
{
NeedSYMBOLorNUMBER();
wxString padName = From_UTF8( CurText() );
NeedRIGHT();
wxCHECK2( currentGroup, continue );
currentGroup->insert( padName );
break;
}
case T_RIGHT:
currentGroup = nullptr;
break;
default:
Expecting( "group or pin" );
}
}
break;
}
default:
// Skip not used data (i.e all other tokens)
skipCurrent();
@ -510,6 +558,9 @@ void KICAD_NETLIST_PARSER::parseComponent()
component->SetFields( fields );
component->SetHumanReadablePath( humanSheetPath );
component->SetComponentClassNames( componentClasses );
component->SetDuplicatePadNumbersAreJumpers( duplicatePinsAreJumpers );
std::ranges::copy( jumperPinGroups,
std::inserter( component->JumperPadGroups(), component->JumperPadGroups().end() ) );
m_netlist->AddComponent( component );
}

View File

@ -99,6 +99,7 @@ public:
m_pinCount = 0;
m_path = aPath;
m_kiids = aKiids;
m_duplicatePadNumbersAreJumpers = false;
}
virtual ~COMPONENT() { };
@ -183,6 +184,12 @@ public:
std::unordered_set<wxString>& GetComponentClassNames() { return m_componentClassNames; }
bool GetDuplicatePadNumbersAreJumpers() const { return m_duplicatePadNumbersAreJumpers; }
void SetDuplicatePadNumbersAreJumpers( bool aEnabled ) { m_duplicatePadNumbersAreJumpers = aEnabled; }
std::vector<std::set<wxString>>& JumperPadGroups() { return m_jumperPadGroups; }
const std::vector<std::set<wxString>>& JumperPadGroups() const { return m_jumperPadGroups; }
private:
std::vector<COMPONENT_NET> m_nets; ///< list of nets shared by the component pins
@ -226,6 +233,13 @@ private:
/// Component classes for this footprint
std::unordered_set<wxString> m_componentClassNames;
/// Jumper pad groups for this footprint
std::vector<std::set<wxString>> m_jumperPadGroups;
/// Flag that this footprint should automatically treat sets of two or more pads with the same
/// number as jumpered pin groups
bool m_duplicatePadNumbersAreJumpers;
static COMPONENT_NET m_emptyNet;
};

View File

@ -1314,6 +1314,28 @@ void PCB_IO_KICAD_SEXPR::format( const FOOTPRINT* aFootprint ) const
m_out->Print( ")" );
}
KICAD_FORMAT::FormatBool( m_out, "duplicate_pad_numbers_are_jumpers",
aFootprint->GetDuplicatePadNumbersAreJumpers() );
const std::vector<std::set<wxString>>& jumperGroups = aFootprint->JumperPadGroups();
if( !jumperGroups.empty() )
{
m_out->Print( "(jumper_pad_groups" );
for( const std::set<wxString>& group : jumperGroups )
{
m_out->Print( "(" );
for( const wxString& padName : group )
m_out->Print( "%s ", m_out->Quotew( padName ).c_str() );
m_out->Print( ")" );
}
m_out->Print( ")" );
}
Format( (BOARD_ITEM*) &aFootprint->Reference() );
Format( (BOARD_ITEM*) &aFootprint->Value() );

View File

@ -180,7 +180,8 @@ class PCB_IO_KICAD_SEXPR; // forward decl
//#define SEXPR_BOARD_FILE_VERSION 20250222 // Hatching for PCB shapes
//#define SEXPR_BOARD_FILE_VERSION 20250228 // ipc-4761 via protection features
//#define SEXPR_BOARD_FILE_VERSION 20250302 // Zone Hatching Offsets
#define SEXPR_BOARD_FILE_VERSION 20250309 // Component class dynamic assignment rules
//#define SEXPR_BOARD_FILE_VERSION 20250309 // Component class dynamic assignment rules
#define SEXPR_BOARD_FILE_VERSION 20250324 // Jumper pads
#define BOARD_FILE_HOST_VERSION 20200825 ///< Earlier files than this include the host tag
#define LEGACY_ARC_FORMATTING 20210925 ///< These were the last to use old arc formatting

View File

@ -4787,6 +4787,41 @@ FOOTPRINT* PCB_IO_KICAD_SEXPR_PARSER::parseFOOTPRINT_unchecked( wxArrayString* a
break;
case T_duplicate_pad_numbers_are_jumpers:
footprint->SetDuplicatePadNumbersAreJumpers( parseBool() );
NeedRIGHT();
break;
case T_jumper_pad_groups:
{
// This should only be formatted if there is at least one group
std::vector<std::set<wxString>>& groups = footprint->JumperPadGroups();
std::set<wxString>* currentGroup = nullptr;
for( token = NextTok(); currentGroup || token != T_RIGHT; token = NextTok() )
{
switch( static_cast<int>( token ) )
{
case T_LEFT:
currentGroup = &groups.emplace_back();
break;
case DSN_STRING:
currentGroup->insert( FromUTF8() );
break;
case T_RIGHT:
currentGroup = nullptr;
break;
default:
Expecting( "list of pad names" );
}
}
break;
}
case T_solder_mask_margin:
footprint->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) );
NeedRIGHT();

View File

LOADING design file

View File

@ -1,6 +1,215 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.8,
"height": 1.27,
"width": 2.54
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
@ -287,7 +496,7 @@
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"netlist": "jumpers.net",
"plot": "",
"pos_files": "",
"specctra_dsn": "",

View File

LOADING design file

View File

@ -1,8 +1,8 @@
(export (version "E")
(design
(source "/Users/jon/src/kicad/qa/data/eeschema/netlists/jumpers/jumpers.kicad_sch")
(date "2025-03-21T17:31:14-0400")
(tool "Eeschema 9.99.0-580-g4821313d25")
(date "2025-03-22T22:55:57-0400")
(tool "Eeschema 9.99.0-581-g93ee71d1e1-dirty")
(sheet (number "1") (name "/") (tstamps "/")
(title_block
(title)
@ -22,9 +22,10 @@
(components
(comp (ref "R1")
(value "22")
(footprint "Resistor_SMD:R_0603_1608Metric")
(description "Resistor")
(fields
(field (name "Footprint"))
(field (name "Footprint") "Resistor_SMD:R_0603_1608Metric")
(field (name "Datasheet"))
(field (name "Description") "Resistor"))
(libsource (lib "Device") (part "R") (description "Resistor"))
@ -36,9 +37,10 @@
(tstamps "4c671539-9120-429d-9b52-8f805a6b0fad"))
(comp (ref "R2")
(value "22")
(footprint "Resistor_SMD:R_0603_1608Metric")
(description "Resistor")
(fields
(field (name "Footprint"))
(field (name "Footprint") "Resistor_SMD:R_0603_1608Metric")
(field (name "Datasheet"))
(field (name "Description") "Resistor"))
(libsource (lib "Device") (part "R") (description "Resistor"))
@ -50,24 +52,30 @@
(tstamps "a5325dfa-f0ed-4395-9ec3-24765fab7875"))
(comp (ref "TB1")
(value "~")
(footprint "Button_Switch_SMD:Panasonic_EVQPUK_EVQPUB")
(fields
(field (name "Footprint"))
(field (name "Footprint") "Button_Switch_SMD:Panasonic_EVQPUK_EVQPUB")
(field (name "Datasheet"))
(field (name "Description")))
(libsource (lib "JumperTest") (part "TerminalBlock_2Level") (description ""))
(property (name "Sheetname") (value "Root"))
(property (name "Sheetfile") (value "jumpers.kicad_sch"))
(duplicate_pin_numbers_are_jumpers "1")
(sheetpath (names "/") (tstamps "/"))
(tstamps "0aa9b267-04ec-4615-b424-0bb2ed02b625"))
(comp (ref "U1")
(value "~")
(footprint "Package_TO_SOT_SMD:SC-82AA")
(fields
(field (name "Footprint"))
(field (name "Footprint") "Package_TO_SOT_SMD:SC-82AA")
(field (name "Datasheet"))
(field (name "Description")))
(libsource (lib "JumperTest") (part "Matrix") (description ""))
(property (name "Sheetname") (value "Root"))
(property (name "Sheetfile") (value "jumpers.kicad_sch"))
(jumper_pin_groups
(group (pin "1") (pin "3"))
(group (pin "2") (pin "4")))
(sheetpath (names "/") (tstamps "/"))
(tstamps "4d14d800-569a-495d-bb5a-a9e55a14d7c8")))
(libparts