mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 02:03:12 +02:00
Compare commits
15 Commits
3a24adb83c
...
9f414f11dc
Author | SHA1 | Date | |
---|---|---|---|
|
9f414f11dc | ||
|
18b56539a6 | ||
|
9b006c4f3b | ||
|
8035a66152 | ||
|
45166bf5c3 | ||
|
6ab6283e2e | ||
|
fc7d91214d | ||
|
dcbadb5857 | ||
|
3b97804cb6 | ||
|
e72def55a9 | ||
|
76e4b4aca4 | ||
|
a391e953e7 | ||
|
7e448c404a | ||
|
1418b03c5a | ||
|
a7e004bd12 |
@ -19,11 +19,23 @@
|
||||
|
||||
#include "lib_table_grid_tricks.h"
|
||||
#include "lib_table_grid.h"
|
||||
#include <wx/clipbrd.h>
|
||||
#include <wx/log.h>
|
||||
|
||||
|
||||
LIB_TABLE_GRID_TRICKS::LIB_TABLE_GRID_TRICKS( WX_GRID* aGrid ) :
|
||||
GRID_TRICKS( aGrid )
|
||||
{
|
||||
m_grid->Disconnect( wxEVT_CHAR_HOOK );
|
||||
m_grid->Connect( wxEVT_CHAR_HOOK, wxCharEventHandler( LIB_TABLE_GRID_TRICKS::onCharHook ), nullptr, this );
|
||||
}
|
||||
|
||||
LIB_TABLE_GRID_TRICKS::LIB_TABLE_GRID_TRICKS( WX_GRID* aGrid,
|
||||
std::function<void( wxCommandEvent& )> aAddHandler ) :
|
||||
GRID_TRICKS( aGrid, aAddHandler )
|
||||
{
|
||||
m_grid->Disconnect( wxEVT_CHAR_HOOK );
|
||||
m_grid->Connect( wxEVT_CHAR_HOOK, wxCharEventHandler( LIB_TABLE_GRID_TRICKS::onCharHook ), nullptr, this );
|
||||
}
|
||||
|
||||
|
||||
@ -134,6 +146,61 @@ void LIB_TABLE_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
|
||||
GRID_TRICKS::doPopupSelection( event );
|
||||
}
|
||||
}
|
||||
void LIB_TABLE_GRID_TRICKS::onCharHook( wxKeyEvent& ev )
|
||||
{
|
||||
if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'V' && m_grid->IsCellEditControlShown() )
|
||||
{
|
||||
wxLogNull doNotLog;
|
||||
|
||||
if( wxTheClipboard->Open() )
|
||||
{
|
||||
if( wxTheClipboard->IsSupported( wxDF_TEXT ) || wxTheClipboard->IsSupported( wxDF_UNICODETEXT ) )
|
||||
{
|
||||
wxTextDataObject data;
|
||||
wxTheClipboard->GetData( data );
|
||||
|
||||
wxString text = data.GetText();
|
||||
|
||||
if( !text.Contains( '\t' ) && text.Contains( ',' ) )
|
||||
text.Replace( ',', '\t' );
|
||||
|
||||
if( text.Contains( '\t' ) || text.Contains( '\n' ) || text.Contains( '\r' ) )
|
||||
{
|
||||
m_grid->CancelPendingChanges();
|
||||
int row = m_grid->GetGridCursorRow();
|
||||
|
||||
// Check if the current row already has data (has a nickname)
|
||||
wxGridTableBase* table = m_grid->GetTable();
|
||||
if( table && row >= 0 && row < table->GetNumberRows() )
|
||||
{
|
||||
// Check if the row has a nickname (indicating it has existing data)
|
||||
wxString nickname = table->GetValue( row, COL_NICKNAME );
|
||||
if( !nickname.IsEmpty() )
|
||||
{
|
||||
// Row already has data, don't allow pasting over it
|
||||
wxTheClipboard->Close();
|
||||
wxBell(); // Provide audio feedback
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_grid->ClearSelection();
|
||||
m_grid->SelectRow( row );
|
||||
m_grid->SetGridCursor( row, 0 );
|
||||
getSelectedArea();
|
||||
paste_text( text );
|
||||
wxTheClipboard->Close();
|
||||
m_grid->ForceRefresh();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
wxTheClipboard->Close();
|
||||
}
|
||||
}
|
||||
|
||||
GRID_TRICKS::onCharHook( ev );
|
||||
}
|
||||
|
||||
|
||||
bool LIB_TABLE_GRID_TRICKS::handleDoubleClick( wxGridEvent& aEvent )
|
||||
|
@ -1160,7 +1160,9 @@ void UOP::Exec( CONTEXT* ctx )
|
||||
return;
|
||||
|
||||
case TR_OP_METHOD_CALL:
|
||||
m_func( ctx, m_ref.get() );
|
||||
if( m_func )
|
||||
m_func( ctx, m_ref.get() );
|
||||
|
||||
return;
|
||||
|
||||
default:
|
||||
@ -1321,9 +1323,8 @@ VALUE* UCODE::Run( CONTEXT* ctx )
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
// rules which fail outright should not be fired
|
||||
std::unique_ptr<VALUE> temp_false = std::make_unique<VALUE>( 0 );
|
||||
return ctx->StoreValue( temp_false.get() );
|
||||
// rules which fail outright should not be fired; return 0/false
|
||||
return ctx->StoreValue( new VALUE( 0 ) );
|
||||
}
|
||||
|
||||
if( ctx->SP() == 1 )
|
||||
@ -1339,8 +1340,7 @@ VALUE* UCODE::Run( CONTEXT* ctx )
|
||||
wxASSERT( ctx->SP() == 1 );
|
||||
|
||||
// non-well-formed rules should not be fired on a release build
|
||||
std::unique_ptr<VALUE> temp_false = std::make_unique<VALUE>( 0 );
|
||||
return ctx->StoreValue( temp_false.get() );
|
||||
return ctx->StoreValue( new VALUE( 0 ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,3 +51,5 @@ value
|
||||
version
|
||||
aliases
|
||||
alias
|
||||
unit
|
||||
units
|
||||
|
@ -370,6 +370,10 @@ true
|
||||
tstamp
|
||||
type
|
||||
units
|
||||
unit
|
||||
pins
|
||||
pin
|
||||
num
|
||||
units_format
|
||||
unlocked
|
||||
user
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include <lib_table_grid.h>
|
||||
#include <wildcards_and_files_ext.h>
|
||||
#include <env_paths.h>
|
||||
#include <functional>
|
||||
#include <eeschema_id.h>
|
||||
#include <symbol_edit_frame.h>
|
||||
#include <symbol_viewer_frame.h>
|
||||
@ -153,6 +154,13 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
SYMBOL_GRID_TRICKS( DIALOG_EDIT_LIBRARY_TABLES* aParent, WX_GRID* aGrid,
|
||||
std::function<void( wxCommandEvent& )> aAddHandler ) :
|
||||
LIB_TABLE_GRID_TRICKS( aGrid, aAddHandler ),
|
||||
m_dialog( aParent )
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
DIALOG_EDIT_LIBRARY_TABLES* m_dialog;
|
||||
|
||||
@ -224,8 +232,21 @@ protected:
|
||||
}
|
||||
else
|
||||
{
|
||||
// paste spreadsheet formatted text.
|
||||
GRID_TRICKS::paste_text( cb_text );
|
||||
wxString text = cb_text;
|
||||
|
||||
if( !text.Contains( '\t' ) && text.Contains( ',' ) )
|
||||
text.Replace( ',', '\t' );
|
||||
|
||||
if( text.Contains( '\t' ) )
|
||||
{
|
||||
int row = m_grid->GetGridCursorRow();
|
||||
m_grid->ClearSelection();
|
||||
m_grid->SelectRow( row );
|
||||
m_grid->SetGridCursor( row, 0 );
|
||||
getSelectedArea();
|
||||
}
|
||||
|
||||
GRID_TRICKS::paste_text( text );
|
||||
|
||||
m_grid->AutoSizeColumns( false );
|
||||
}
|
||||
@ -250,7 +271,8 @@ void PANEL_SYM_LIB_TABLE::setupGrid( WX_GRID* aGrid )
|
||||
};
|
||||
|
||||
// add Cut, Copy, and Paste to wxGrids
|
||||
aGrid->PushEventHandler( new SYMBOL_GRID_TRICKS( m_parent, aGrid ) );
|
||||
aGrid->PushEventHandler( new SYMBOL_GRID_TRICKS( m_parent, aGrid,
|
||||
[this]( wxCommandEvent& event ) { appendRowHandler( event ); } ) );
|
||||
|
||||
aGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
|
||||
|
||||
|
@ -481,6 +481,80 @@ XNODE* NETLIST_EXPORTER_XML::makeSymbols( unsigned aCtl )
|
||||
// Output the primary UUID
|
||||
uuid = symbol->m_Uuid.AsString();
|
||||
xunits->AddChild( new XNODE( wxXML_TEXT_NODE, wxEmptyString, uuid ) );
|
||||
|
||||
// Emit unit information (per-unit name and pins) after tstamps
|
||||
XNODE* xunitInfo;
|
||||
xcomp->AddChild( xunitInfo = node( wxT( "units" ) ) );
|
||||
|
||||
// Emit all units defined by the library symbol, independent of placement
|
||||
const std::unique_ptr<LIB_SYMBOL>& libSym = symbol->GetLibSymbolRef();
|
||||
|
||||
if( libSym )
|
||||
{
|
||||
int unitCount = std::max( libSym->GetUnitCount(), 1 );
|
||||
|
||||
for( int unitIdx = 1; unitIdx <= unitCount; ++unitIdx )
|
||||
{
|
||||
wxString unitName = libSym->GetUnitDisplayName( unitIdx, false );
|
||||
|
||||
XNODE* xunit;
|
||||
xunitInfo->AddChild( xunit = node( wxT( "unit" ) ) );
|
||||
xunit->AddAttribute( wxT( "name" ), unitName );
|
||||
|
||||
XNODE* xpins;
|
||||
xunit->AddChild( xpins = node( wxT( "pins" ) ) );
|
||||
|
||||
// Gather all graphical pins for this unit across body styles
|
||||
std::vector<SCH_PIN*> pinList = libSym->GetGraphicalPins( unitIdx, 0 );
|
||||
|
||||
// Sort by X then Y to establish a stable, spatial order for matching pins
|
||||
// in the PCB editor when swapping gates
|
||||
std::sort( pinList.begin(), pinList.end(),
|
||||
[]( SCH_PIN* a, SCH_PIN* b )
|
||||
{
|
||||
auto pa = a->GetPosition();
|
||||
auto pb = b->GetPosition();
|
||||
|
||||
if( pa.x != pb.x )
|
||||
return pa.x < pb.x;
|
||||
|
||||
return pa.y < pb.y;
|
||||
} );
|
||||
|
||||
// Emit pins in this spatial order, deduping by number within the unit only
|
||||
std::unordered_set<wxString> seen;
|
||||
|
||||
for( SCH_PIN* basePin : pinList )
|
||||
{
|
||||
bool stackedValid = false;
|
||||
std::vector<wxString> expandedNums = basePin->GetStackedPinNumbers( &stackedValid );
|
||||
|
||||
if( stackedValid && !expandedNums.empty() )
|
||||
{
|
||||
for( const wxString& num : expandedNums )
|
||||
{
|
||||
if( seen.insert( num ).second )
|
||||
{
|
||||
XNODE* xpin;
|
||||
xpins->AddChild( xpin = node( wxT( "pin" ) ) );
|
||||
xpin->AddAttribute( wxT( "num" ), num );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wxString num = basePin->GetShownNumber();
|
||||
|
||||
if( seen.insert( num ).second )
|
||||
{
|
||||
XNODE* xpin;
|
||||
xpins->AddChild( xpin = node( wxT( "pin" ) ) );
|
||||
xpin->AddAttribute( wxT( "num" ), num );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -770,6 +770,12 @@ TOOL_ACTION SCH_ACTIONS::swap( TOOL_ACTION_ARGS()
|
||||
.Tooltip( _( "Swap positions of selected items" ) )
|
||||
.Icon( BITMAPS::swap ) );
|
||||
|
||||
TOOL_ACTION SCH_ACTIONS::swapPinLabels( TOOL_ACTION_ARGS()
|
||||
.Name( "eeschema.InteractiveEdit.swapPinLabels" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.FriendlyName( _( "Swap Pin Labels" ) )
|
||||
.Tooltip( _( "Swap the labels attached to selected pins" ) ) );
|
||||
|
||||
TOOL_ACTION SCH_ACTIONS::properties( TOOL_ACTION_ARGS()
|
||||
.Name( "eeschema.InteractiveEdit.properties" )
|
||||
.Scope( AS_GLOBAL )
|
||||
|
@ -122,6 +122,7 @@ public:
|
||||
static TOOL_ACTION mirrorV;
|
||||
static TOOL_ACTION mirrorH;
|
||||
static TOOL_ACTION swap;
|
||||
static TOOL_ACTION swapPinLabels;
|
||||
static TOOL_ACTION properties;
|
||||
static TOOL_ACTION editReference;
|
||||
static TOOL_ACTION editValue;
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <tools/sch_move_tool.h>
|
||||
#include <tools/sch_drawing_tools.h>
|
||||
#include <confirm.h>
|
||||
#include <connection_graph.h>
|
||||
#include <sch_actions.h>
|
||||
#include <sch_tool_utils.h>
|
||||
#include <increment.h>
|
||||
@ -40,9 +41,11 @@
|
||||
#include <sch_bus_entry.h>
|
||||
#include <sch_commit.h>
|
||||
#include <sch_group.h>
|
||||
#include <sch_label.h>
|
||||
#include <sch_junction.h>
|
||||
#include <sch_marker.h>
|
||||
#include <sch_rule_area.h>
|
||||
#include <sch_pin.h>
|
||||
#include <sch_sheet_pin.h>
|
||||
#include <sch_textbox.h>
|
||||
#include <sch_table.h>
|
||||
@ -1516,6 +1519,104 @@ int SCH_EDIT_TOOL::Swap( const TOOL_EVENT& aEvent )
|
||||
}
|
||||
|
||||
|
||||
int SCH_EDIT_TOOL::SwapPinLabels( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
SCH_SELECTION& selection = m_selectionTool->RequestSelection( { SCH_PIN_T } );
|
||||
std::vector<EDA_ITEM*> orderedPins = selection.GetItemsSortedBySelectionOrder();
|
||||
|
||||
if( orderedPins.size() < 2 )
|
||||
return 0;
|
||||
|
||||
CONNECTION_GRAPH* connectionGraph = m_frame->Schematic().ConnectionGraph();
|
||||
|
||||
const SCH_SHEET_PATH& sheetPath = m_frame->GetCurrentSheet();
|
||||
|
||||
auto findSingleNetLabel = [&]( SCH_PIN* aPin ) -> SCH_LABEL_BASE*
|
||||
{
|
||||
CONNECTION_SUBGRAPH* sg = connectionGraph->GetSubgraphForItem( aPin );
|
||||
|
||||
if( !sg )
|
||||
return nullptr;
|
||||
|
||||
const std::set<SCH_ITEM*>& items = sg->GetItems();
|
||||
|
||||
size_t pinCount = 0;
|
||||
SCH_LABEL_BASE* label = nullptr;
|
||||
|
||||
for( SCH_ITEM* it : items )
|
||||
{
|
||||
if( it->Type() == SCH_PIN_T )
|
||||
pinCount++;
|
||||
|
||||
switch( it->Type() )
|
||||
{
|
||||
case SCH_LABEL_T:
|
||||
case SCH_GLOBAL_LABEL_T:
|
||||
case SCH_HIER_LABEL_T:
|
||||
{
|
||||
SCH_CONNECTION* conn = it->Connection( &sheetPath );
|
||||
|
||||
if( conn && conn->IsNet() )
|
||||
{
|
||||
if( label )
|
||||
return nullptr; // more than one label
|
||||
|
||||
label = static_cast<SCH_LABEL_BASE*>( it );
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if( pinCount != 1 )
|
||||
return nullptr;
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
std::vector<SCH_LABEL_BASE*> labels;
|
||||
|
||||
for( EDA_ITEM* item : orderedPins )
|
||||
{
|
||||
SCH_PIN* pin = static_cast<SCH_PIN*>( item );
|
||||
SCH_LABEL_BASE* label = findSingleNetLabel( pin );
|
||||
|
||||
if( !label )
|
||||
{
|
||||
m_frame->ShowInfoBarError(
|
||||
_( "Each selected pin must have exactly one attached net label and no other pin connections." ) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
labels.push_back( label );
|
||||
}
|
||||
|
||||
if( labels.size() >= 2 )
|
||||
{
|
||||
SCH_COMMIT commit( m_frame );
|
||||
|
||||
for( SCH_LABEL_BASE* lb : labels )
|
||||
commit.Modify( lb, m_frame->GetScreen() );
|
||||
|
||||
for( size_t i = 0; i < labels.size() - 1; ++i )
|
||||
{
|
||||
SCH_LABEL_BASE* a = labels[i];
|
||||
SCH_LABEL_BASE* b = labels[( i + 1 ) % labels.size()];
|
||||
wxString aText = a->GetText();
|
||||
wxString bText = b->GetText();
|
||||
a->SetText( bText );
|
||||
b->SetText( aText );
|
||||
}
|
||||
|
||||
commit.Push( _( "Swap Pin Labels" ) );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int SCH_EDIT_TOOL::RepeatDrawItem( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
const std::vector<std::unique_ptr<SCH_ITEM>>& sourceItems = m_frame->GetRepeatItems();
|
||||
@ -3162,6 +3263,7 @@ void SCH_EDIT_TOOL::setTransitions()
|
||||
Go( &SCH_EDIT_TOOL::Mirror, SCH_ACTIONS::mirrorV.MakeEvent() );
|
||||
Go( &SCH_EDIT_TOOL::Mirror, SCH_ACTIONS::mirrorH.MakeEvent() );
|
||||
Go( &SCH_EDIT_TOOL::Swap, SCH_ACTIONS::swap.MakeEvent() );
|
||||
Go( &SCH_EDIT_TOOL::SwapPinLabels, SCH_ACTIONS::swapPinLabels.MakeEvent() );
|
||||
Go( &SCH_EDIT_TOOL::DoDelete, ACTIONS::doDelete.MakeEvent() );
|
||||
Go( &SCH_EDIT_TOOL::InteractiveDelete, ACTIONS::deleteTool.MakeEvent() );
|
||||
|
||||
|
@ -42,6 +42,7 @@ public:
|
||||
int Rotate( const TOOL_EVENT& aEvent );
|
||||
int Mirror( const TOOL_EVENT& aEvent );
|
||||
int Swap( const TOOL_EVENT& aEvent );
|
||||
int SwapPinLabels( const TOOL_EVENT& aEvent );
|
||||
|
||||
int RepeatDrawItem( const TOOL_EVENT& aEvent );
|
||||
|
||||
|
@ -46,6 +46,9 @@
|
||||
#include <pgm_base.h>
|
||||
#include <view/view_controls.h>
|
||||
#include <settings/settings_manager.h>
|
||||
#include <math/box2.h>
|
||||
#include <base_units.h>
|
||||
#include <sch_screen.h>
|
||||
#include "sch_move_tool.h"
|
||||
|
||||
|
||||
@ -500,6 +503,7 @@ bool SCH_MOVE_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, SCH_COMMIT* aComm
|
||||
TOOL_EVENT* evt = ©
|
||||
VECTOR2I prevPos;
|
||||
GRID_HELPER_GRIDS snapLayer = GRID_CURRENT;
|
||||
SCH_SHEET* hoverSheet = nullptr;
|
||||
|
||||
m_cursor = controls->GetCursorPosition();
|
||||
|
||||
@ -771,7 +775,65 @@ bool SCH_MOVE_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, SCH_COMMIT* aComm
|
||||
|
||||
m_cursor = grid.BestSnapAnchor( controls->GetCursorPosition( false ),
|
||||
snapLayer, selection );
|
||||
// Determine potential target sheet.
|
||||
SCH_SHEET* sheet = dynamic_cast<SCH_SHEET*>( m_frame->GetScreen()->GetItem( m_cursor, 0,
|
||||
SCH_SHEET_T ) );
|
||||
if( sheet && sheet->IsSelected() )
|
||||
sheet = nullptr; // Never target a selected sheet
|
||||
|
||||
if( !sheet )
|
||||
{
|
||||
// Build current selection bounding box in its (already moved) position.
|
||||
BOX2I selBBox;
|
||||
for( EDA_ITEM* it : selection )
|
||||
{
|
||||
if( SCH_ITEM* schIt = dynamic_cast<SCH_ITEM*>( it ) )
|
||||
selBBox.Merge( schIt->GetBoundingBox() );
|
||||
}
|
||||
|
||||
if( selBBox.GetWidth() > 0 && selBBox.GetHeight() > 0 )
|
||||
{
|
||||
VECTOR2I selCenter( selBBox.GetX() + selBBox.GetWidth() / 2,
|
||||
selBBox.GetY() + selBBox.GetHeight() / 2 );
|
||||
|
||||
// Find first non-selected sheet whose body fully contains the selection
|
||||
// or at least contains its center point.
|
||||
for( SCH_ITEM* it : m_frame->GetScreen()->Items().OfType( SCH_SHEET_T ) )
|
||||
{
|
||||
SCH_SHEET* candidate = static_cast<SCH_SHEET*>( it );
|
||||
if( candidate->IsSelected() || candidate->IsRootSheet() )
|
||||
continue;
|
||||
|
||||
BOX2I body = candidate->GetBodyBoundingBox();
|
||||
|
||||
if( body.Contains( selBBox ) || body.Contains( selCenter ) )
|
||||
{
|
||||
sheet = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( sheet != hoverSheet )
|
||||
{
|
||||
if( hoverSheet )
|
||||
{
|
||||
hoverSheet->ClearFlags( BRIGHTENED );
|
||||
m_frame->UpdateItem( hoverSheet, false );
|
||||
}
|
||||
|
||||
hoverSheet = sheet;
|
||||
|
||||
if( hoverSheet )
|
||||
{
|
||||
hoverSheet->SetFlags( BRIGHTENED );
|
||||
m_frame->UpdateItem( hoverSheet, false );
|
||||
}
|
||||
}
|
||||
|
||||
m_frame->GetCanvas()->SetCurrentCursor( hoverSheet ? KICURSOR::PLACE
|
||||
: KICURSOR::MOVING );
|
||||
VECTOR2I delta( m_cursor - prevPos );
|
||||
m_anchorPos = m_cursor;
|
||||
|
||||
@ -1032,6 +1094,22 @@ bool SCH_MOVE_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, SCH_COMMIT* aComm
|
||||
|
||||
} while( ( evt = Wait() ) ); //Should be assignment not equality test
|
||||
|
||||
SCH_SHEET* targetSheet = hoverSheet;
|
||||
|
||||
if( hoverSheet )
|
||||
{
|
||||
hoverSheet->ClearFlags( BRIGHTENED );
|
||||
m_frame->UpdateItem( hoverSheet, false );
|
||||
}
|
||||
|
||||
if( targetSheet )
|
||||
{
|
||||
moveSelectionToSheet( selection, targetSheet, aCommit );
|
||||
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
||||
m_newDragLines.clear();
|
||||
m_changedDragLines.clear();
|
||||
}
|
||||
|
||||
// Create a selection of original selection, drag selected/changed items, and new
|
||||
// bend lines for later before we clear them in the aCommit. We'll need these
|
||||
// to check for new junctions needed, etc.
|
||||
@ -1135,6 +1213,60 @@ bool SCH_MOVE_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, SCH_COMMIT* aComm
|
||||
}
|
||||
|
||||
|
||||
void SCH_MOVE_TOOL::moveSelectionToSheet( SCH_SELECTION& aSelection, SCH_SHEET* aTargetSheet,
|
||||
SCH_COMMIT* aCommit )
|
||||
{
|
||||
SCH_SCREEN* destScreen = aTargetSheet->GetScreen();
|
||||
SCH_SCREEN* srcScreen = m_frame->GetScreen();
|
||||
|
||||
BOX2I bbox;
|
||||
|
||||
for( EDA_ITEM* item : aSelection )
|
||||
bbox.Merge( static_cast<SCH_ITEM*>( item )->GetBoundingBox() );
|
||||
|
||||
VECTOR2I offset = VECTOR2I( 0, 0 ) - bbox.GetPosition();
|
||||
int step = schIUScale.MilsToIU( 50 );
|
||||
bool overlap = false;
|
||||
|
||||
do
|
||||
{
|
||||
BOX2I moved = bbox;
|
||||
moved.Move( offset );
|
||||
overlap = false;
|
||||
|
||||
for( SCH_ITEM* existing : destScreen->Items() )
|
||||
{
|
||||
if( moved.Intersects( existing->GetBoundingBox() ) )
|
||||
{
|
||||
overlap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( overlap )
|
||||
offset += VECTOR2I( step, step );
|
||||
} while( overlap );
|
||||
|
||||
for( EDA_ITEM* item : aSelection )
|
||||
{
|
||||
SCH_ITEM* schItem = static_cast<SCH_ITEM*>( item );
|
||||
|
||||
// Remove from current screen and view manually
|
||||
m_frame->RemoveFromScreen( schItem, srcScreen );
|
||||
|
||||
// Move the item
|
||||
schItem->Move( offset );
|
||||
|
||||
// Add to destination screen manually (won't add to view since it's not current)
|
||||
destScreen->Append( schItem );
|
||||
|
||||
// Record in commit with CHT_DONE flag to bypass automatic screen/view operations
|
||||
aCommit->Stage( schItem, CHT_REMOVE | CHT_DONE, srcScreen );
|
||||
aCommit->Stage( schItem, CHT_ADD | CHT_DONE, destScreen );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SCH_MOVE_TOOL::trimDanglingLines( SCH_COMMIT* aCommit )
|
||||
{
|
||||
// Need a local cleanup first to ensure we remove unneeded junctions
|
||||
@ -1163,8 +1295,7 @@ void SCH_MOVE_TOOL::trimDanglingLines( SCH_COMMIT* aCommit )
|
||||
{
|
||||
line->SetFlags( STRUCT_DELETED );
|
||||
aCommit->Removed( line, m_frame->GetScreen() );
|
||||
|
||||
updateItem( line, false );
|
||||
updateItem( line, false ); // Update any cached visuals before commit processes
|
||||
m_frame->RemoveFromScreen( line, m_frame->GetScreen() );
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,9 @@ class SCH_LINE;
|
||||
class SCH_LABEL_BASE;
|
||||
class SCH_SHEET_PIN;
|
||||
class SCH_JUNCTION;
|
||||
class SCH_SELECTION;
|
||||
class SCH_SHEET;
|
||||
class SCH_COMMIT;
|
||||
|
||||
|
||||
struct SPECIAL_CASE_LABEL_INFO
|
||||
@ -81,6 +84,8 @@ private:
|
||||
void orthoLineDrag( SCH_COMMIT* aCommit, SCH_LINE* line, const VECTOR2I& splitDelta,
|
||||
int& xBendCount, int& yBendCount, const EE_GRID_HELPER& grid );
|
||||
|
||||
void moveSelectionToSheet( SCH_SELECTION& aSelection, SCH_SHEET* aTarget, SCH_COMMIT* aCommit );
|
||||
|
||||
///< Clears the new drag lines and removes them from the screen
|
||||
void clearNewDragLines();
|
||||
|
||||
|
@ -250,6 +250,7 @@ bool SCH_SELECTION_TOOL::Init()
|
||||
auto sheetSelection = SCH_CONDITIONS::Count( 1 ) && SCH_CONDITIONS::OnlyTypes( sheetTypes );
|
||||
auto crossProbingSelection = SCH_CONDITIONS::MoreThan( 0 ) && SCH_CONDITIONS::HasTypes( crossProbingTypes );
|
||||
auto tableCellSelection = SCH_CONDITIONS::MoreThan( 0 ) && SCH_CONDITIONS::OnlyTypes( tableCellTypes );
|
||||
auto multiplePinsSelection = SCH_CONDITIONS::MoreThan( 1 ) && SCH_CONDITIONS::OnlyTypes( { SCH_PIN_T } );
|
||||
// clang-format on
|
||||
|
||||
auto schEditSheetPageNumberCondition =
|
||||
@ -338,6 +339,7 @@ bool SCH_SELECTION_TOOL::Init()
|
||||
menu.AddItem( SCH_ACTIONS::autoplaceAllSheetPins, sheetSelection && SCH_CONDITIONS::Idle, 250 );
|
||||
menu.AddItem( SCH_ACTIONS::syncSheetPins, sheetSelection && SCH_CONDITIONS::Idle, 250 );
|
||||
menu.AddItem( SCH_ACTIONS::assignNetclass, connectedSelection && SCH_CONDITIONS::Idle, 250 );
|
||||
menu.AddItem( SCH_ACTIONS::swapPinLabels, multiplePinsSelection && schEditCondition && SCH_CONDITIONS::Idle, 250 );
|
||||
menu.AddItem( SCH_ACTIONS::editPageNumber, schEditSheetPageNumberCondition, 250 );
|
||||
|
||||
menu.AddSeparator( 400 );
|
||||
|
@ -671,8 +671,7 @@ bool GERBER_FILE_IMAGE::ExecuteRS274XCommand( int aCommand, char* aBuff,
|
||||
break;
|
||||
|
||||
case IMAGE_POLARITY:
|
||||
// These commands are deprecated since 2012.
|
||||
// So do nothing and prompt the user about this command
|
||||
// Note: these commands IPPOS and IPNEG are deprecated since 2012.
|
||||
if( strncasecmp( aText, "NEG", 3 ) == 0 )
|
||||
{
|
||||
m_ImageNegative = true;
|
||||
@ -688,7 +687,6 @@ bool GERBER_FILE_IMAGE::ExecuteRS274XCommand( int aCommand, char* aBuff,
|
||||
// actual effect. Just skip it.
|
||||
}
|
||||
|
||||
ok = false;
|
||||
break;
|
||||
|
||||
case LOAD_POLARITY:
|
||||
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "grid_tricks.h"
|
||||
#include <functional>
|
||||
|
||||
class LIB_TABLE_GRID_TRICKS : public GRID_TRICKS
|
||||
{
|
||||
@ -33,6 +34,7 @@ class LIB_TABLE_GRID_TRICKS : public GRID_TRICKS
|
||||
|
||||
public:
|
||||
explicit LIB_TABLE_GRID_TRICKS( WX_GRID* aGrid );
|
||||
LIB_TABLE_GRID_TRICKS( WX_GRID* aGrid, std::function<void( wxCommandEvent& )> aAddHandler );
|
||||
|
||||
virtual ~LIB_TABLE_GRID_TRICKS(){};
|
||||
|
||||
@ -43,5 +45,7 @@ protected:
|
||||
virtual void optionsEditor( int aRow ) = 0;
|
||||
bool handleDoubleClick( wxGridEvent& aEvent ) override;
|
||||
|
||||
void onCharHook( wxKeyEvent& ev );
|
||||
|
||||
virtual bool supportsVisibilityColumn() { return false; }
|
||||
};
|
||||
|
@ -327,7 +327,9 @@ class INFOBAR_REPORTER : public REPORTER
|
||||
{
|
||||
public:
|
||||
INFOBAR_REPORTER( WX_INFOBAR* aInfoBar ) :
|
||||
REPORTER(), m_messageSet( false ), m_infoBar( aInfoBar ),
|
||||
REPORTER(),
|
||||
m_messageSet( false ),
|
||||
m_infoBar( aInfoBar ),
|
||||
m_severity( RPT_SEVERITY_UNDEFINED )
|
||||
{
|
||||
}
|
||||
|
@ -55,8 +55,8 @@
|
||||
|
||||
#define RESOLVE_PAGE( T, pageIndex ) static_cast<T*>( m_treebook->ResolvePage( pageIndex ) )
|
||||
|
||||
DIALOG_BOARD_SETUP::DIALOG_BOARD_SETUP( PCB_EDIT_FRAME* aFrame ) :
|
||||
PAGED_DIALOG( aFrame, _( "Board Setup" ), false, false,
|
||||
DIALOG_BOARD_SETUP::DIALOG_BOARD_SETUP( PCB_EDIT_FRAME* aFrame, wxWindow* aParent ) :
|
||||
PAGED_DIALOG( aParent ? aParent : aFrame, _( "Board Setup" ), false, false,
|
||||
_( "Import Settings from Another Board..." ), wxSize( 980, 600 ) ),
|
||||
m_frame( aFrame ),
|
||||
m_layers( nullptr ),
|
||||
|
@ -42,7 +42,7 @@ class PANEL_TEXT_VARIABLES;
|
||||
class DIALOG_BOARD_SETUP : public PAGED_DIALOG
|
||||
{
|
||||
public:
|
||||
DIALOG_BOARD_SETUP( PCB_EDIT_FRAME* aFrame );
|
||||
DIALOG_BOARD_SETUP( PCB_EDIT_FRAME* aFrame, wxWindow* aWindow = nullptr );
|
||||
~DIALOG_BOARD_SETUP();
|
||||
|
||||
protected:
|
||||
|
@ -303,7 +303,7 @@ void DIALOG_DRC::OnMenu( wxCommandEvent& event )
|
||||
|
||||
void DIALOG_DRC::OnErrorLinkClicked( wxHtmlLinkEvent& event )
|
||||
{
|
||||
m_frame->ShowBoardSetupDialog( _( "Custom Rules" ) );
|
||||
m_frame->ShowBoardSetupDialog( _( "Custom Rules" ), this );
|
||||
}
|
||||
|
||||
|
||||
@ -993,7 +993,7 @@ void DIALOG_DRC::OnDRCItemRClick( wxDataViewEvent& aEvent )
|
||||
}
|
||||
|
||||
case ID_EDIT_SEVERITIES:
|
||||
m_frame->ShowBoardSetupDialog( _( "Violation Severity" ) );
|
||||
m_frame->ShowBoardSetupDialog( _( "Violation Severity" ), this );
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1035,7 +1035,7 @@ void DIALOG_DRC::OnIgnoredItemRClick( wxListEvent& event )
|
||||
|
||||
void DIALOG_DRC::OnEditViolationSeverities( wxHyperlinkEvent& aEvent )
|
||||
{
|
||||
m_frame->ShowBoardSetupDialog( _( "Violation Severity" ) );
|
||||
m_frame->ShowBoardSetupDialog( _( "Violation Severity" ), this );
|
||||
}
|
||||
|
||||
|
||||
|
@ -674,6 +674,7 @@ bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::Validate()
|
||||
// Check that the user isn't trying to remove a layer that is used by the footprint
|
||||
usedLayers &= ~getCustomLayersFromControls();
|
||||
usedLayers &= ~LSET::AllTechMask();
|
||||
usedLayers &= ~LSET::UserMask();
|
||||
|
||||
if( usedLayers.any() )
|
||||
{
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include <wx/dirdlg.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <functional>
|
||||
|
||||
#include <project.h>
|
||||
#include <env_vars.h>
|
||||
@ -147,6 +148,12 @@ public:
|
||||
m_dialog( aParent )
|
||||
{ }
|
||||
|
||||
FP_GRID_TRICKS( DIALOG_EDIT_LIBRARY_TABLES* aParent, WX_GRID* aGrid,
|
||||
std::function<void( wxCommandEvent& )> aAddHandler ) :
|
||||
LIB_TABLE_GRID_TRICKS( aGrid, aAddHandler ),
|
||||
m_dialog( aParent )
|
||||
{ }
|
||||
|
||||
protected:
|
||||
DIALOG_EDIT_LIBRARY_TABLES* m_dialog;
|
||||
|
||||
@ -217,8 +224,21 @@ protected:
|
||||
}
|
||||
else
|
||||
{
|
||||
// paste spreadsheet formatted text.
|
||||
GRID_TRICKS::paste_text( cb_text );
|
||||
wxString text = cb_text;
|
||||
|
||||
if( !text.Contains( '\t' ) && text.Contains( ',' ) )
|
||||
text.Replace( ',', '\t' );
|
||||
|
||||
if( text.Contains( '\t' ) )
|
||||
{
|
||||
int row = m_grid->GetGridCursorRow();
|
||||
m_grid->ClearSelection();
|
||||
m_grid->SelectRow( row );
|
||||
m_grid->SetGridCursor( row, 0 );
|
||||
getSelectedArea();
|
||||
}
|
||||
|
||||
GRID_TRICKS::paste_text( text );
|
||||
|
||||
m_grid->AutoSizeColumns( false );
|
||||
}
|
||||
@ -254,7 +274,8 @@ void PANEL_FP_LIB_TABLE::setupGrid( WX_GRID* aGrid )
|
||||
aGrid->SetRowSize( ii, aGrid->GetDefaultRowSize() + 4 );
|
||||
|
||||
// add Cut, Copy, and Paste to wxGrids
|
||||
aGrid->PushEventHandler( new FP_GRID_TRICKS( m_parent, aGrid ) );
|
||||
aGrid->PushEventHandler( new FP_GRID_TRICKS( m_parent, aGrid,
|
||||
[this]( wxCommandEvent& event ) { appendRowHandler( event ); } ) );
|
||||
|
||||
aGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
|
||||
|
||||
|
@ -527,10 +527,9 @@ void DRC_ENGINE::loadRules( const wxFileName& aPath )
|
||||
void DRC_ENGINE::compileRules()
|
||||
{
|
||||
if( m_logReporter )
|
||||
{
|
||||
m_logReporter->Report( ( wxString::Format( wxT( "Compiling Rules (%d rules): " ),
|
||||
(int) m_rules.size() ) ) );
|
||||
}
|
||||
m_logReporter->Report( wxT( "Compiling Rules" ) );
|
||||
|
||||
REPORTER error_semaphore;
|
||||
|
||||
for( std::shared_ptr<DRC_RULE>& rule : m_rules )
|
||||
{
|
||||
@ -539,9 +538,12 @@ void DRC_ENGINE::compileRules()
|
||||
if( rule->m_Condition && !rule->m_Condition->GetExpression().IsEmpty() )
|
||||
{
|
||||
condition = rule->m_Condition;
|
||||
condition->Compile( nullptr );
|
||||
condition->Compile( &error_semaphore );
|
||||
}
|
||||
|
||||
if( error_semaphore.HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
|
||||
THROW_PARSE_ERROR( wxT( "Parse error" ), rule->m_Name, rule->m_Condition->GetExpression(), 0, 0 );
|
||||
|
||||
for( const DRC_CONSTRAINT& constraint : rule->m_Constraints )
|
||||
{
|
||||
if( !m_constraintMap.count( constraint.m_Type ) )
|
||||
@ -594,6 +596,8 @@ void DRC_ENGINE::InitEngine( const wxFileName& aRulePath )
|
||||
}
|
||||
catch( PARSE_ERROR& original_parse_error )
|
||||
{
|
||||
m_rules.clear();
|
||||
|
||||
try // try again with just our implicit rules
|
||||
{
|
||||
loadImplicitRules();
|
||||
|
@ -750,6 +750,15 @@ public:
|
||||
void ApplyDefaultSettings( const BOARD& board, bool aStyleFields, bool aStyleText,
|
||||
bool aStyleShapes );
|
||||
|
||||
struct FP_UNIT_INFO
|
||||
{
|
||||
wxString m_unitName; // e.g. A
|
||||
std::vector<wxString> m_pins; // pin numbers in this unit
|
||||
};
|
||||
|
||||
void SetUnitInfo( const std::vector<FP_UNIT_INFO>& aUnits ) { m_unitInfo = aUnits; }
|
||||
const std::vector<FP_UNIT_INFO>& GetUnitInfo() const { return m_unitInfo; }
|
||||
|
||||
bool IsBoardOnly() const { return m_attributes & FP_BOARD_ONLY; }
|
||||
void SetBoardOnly( bool aIsBoardOnly = true )
|
||||
{
|
||||
@ -1180,6 +1189,9 @@ private:
|
||||
|
||||
std::unordered_set<wxString> m_transientComponentClassNames;
|
||||
std::unique_ptr<COMPONENT_CLASS_CACHE_PROXY> m_componentClassCacheProxy;
|
||||
|
||||
// Optional unit mapping information for multi-unit symbols
|
||||
std::vector<FP_UNIT_INFO> m_unitInfo;
|
||||
};
|
||||
|
||||
#endif // FOOTPRINT_H
|
||||
|
@ -1166,6 +1166,67 @@ bool BOARD_NETLIST_UPDATER::updateComponentPadConnections( FOOTPRINT* aFootprint
|
||||
}
|
||||
|
||||
|
||||
bool BOARD_NETLIST_UPDATER::updateComponentUnits( FOOTPRINT* aFootprint, COMPONENT* aNewComponent )
|
||||
{
|
||||
// Build the footprint-side representation from the netlist component
|
||||
std::vector<FOOTPRINT::FP_UNIT_INFO> newUnits;
|
||||
|
||||
for( const COMPONENT::UNIT_INFO& u : aNewComponent->GetUnitInfo() )
|
||||
newUnits.push_back( { u.m_unitName, u.m_pins } );
|
||||
|
||||
const std::vector<FOOTPRINT::FP_UNIT_INFO>& curUnits = aFootprint->GetUnitInfo();
|
||||
|
||||
auto unitsEqual = []( const std::vector<FOOTPRINT::FP_UNIT_INFO>& a,
|
||||
const std::vector<FOOTPRINT::FP_UNIT_INFO>& b )
|
||||
{
|
||||
if( a.size() != b.size() )
|
||||
return false;
|
||||
|
||||
for( size_t i = 0; i < a.size(); ++i )
|
||||
{
|
||||
if( a[i].m_unitName != b[i].m_unitName )
|
||||
return false;
|
||||
|
||||
if( a[i].m_pins != b[i].m_pins )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
if( unitsEqual( curUnits, newUnits ) )
|
||||
return false;
|
||||
|
||||
wxString msg;
|
||||
|
||||
if( m_isDryRun )
|
||||
{
|
||||
msg.Printf( _( "Update %s unit metadata." ), aFootprint->GetReference() );
|
||||
m_reporter->Report( msg, RPT_SEVERITY_ACTION );
|
||||
return false; // no actual change on board during dry run
|
||||
}
|
||||
|
||||
// Create a copy only if the footprint has not been added during this update
|
||||
FOOTPRINT* copy = nullptr;
|
||||
|
||||
if( !m_commit.GetStatus( aFootprint ) )
|
||||
{
|
||||
copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
|
||||
copy->SetParentGroup( nullptr );
|
||||
}
|
||||
|
||||
aFootprint->SetUnitInfo( newUnits );
|
||||
|
||||
msg.Printf( _( "Updated %s unit metadata." ), aFootprint->GetReference() );
|
||||
m_reporter->Report( msg, RPT_SEVERITY_ACTION );
|
||||
|
||||
if( copy )
|
||||
m_commit.Modified( aFootprint, copy );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void BOARD_NETLIST_UPDATER::cacheCopperZoneConnections()
|
||||
{
|
||||
for( ZONE* zone : m_board->Zones() )
|
||||
@ -1567,6 +1628,7 @@ bool BOARD_NETLIST_UPDATER::UpdateNetlist( NETLIST& aNetlist )
|
||||
updateFootprintGroup( tmp, component );
|
||||
updateComponentPadConnections( tmp, component );
|
||||
updateComponentClass( tmp, component );
|
||||
updateComponentUnits( tmp, component );
|
||||
|
||||
sheetPaths.insert( footprint->GetSheetname() );
|
||||
}
|
||||
@ -1593,6 +1655,7 @@ bool BOARD_NETLIST_UPDATER::UpdateNetlist( NETLIST& aNetlist )
|
||||
updateFootprintGroup( footprint, component );
|
||||
updateComponentPadConnections( footprint, component );
|
||||
updateComponentClass( footprint, component );
|
||||
updateComponentUnits( footprint, component );
|
||||
|
||||
sheetPaths.insert( footprint->GetSheetname() );
|
||||
}
|
||||
|
@ -121,6 +121,8 @@ private:
|
||||
|
||||
void updateComponentClass( FOOTPRINT* aFootprint, COMPONENT* aNewComponent );
|
||||
|
||||
bool updateComponentUnits( FOOTPRINT* aFootprint, COMPONENT* aNewComponent );
|
||||
|
||||
void cacheCopperZoneConnections();
|
||||
|
||||
bool updateCopperZoneNets( NETLIST& aNetlist );
|
||||
|
@ -332,6 +332,8 @@ void KICAD_NETLIST_PARSER::parseComponent()
|
||||
bool duplicatePinsAreJumpers = false;
|
||||
std::vector<std::set<wxString>> jumperPinGroups;
|
||||
|
||||
std::vector<COMPONENT::UNIT_INFO> parsedUnits;
|
||||
|
||||
// The token comp was read, so the next data is (ref P1)
|
||||
while( (token = NextTok() ) != T_RIGHT )
|
||||
{
|
||||
@ -496,6 +498,87 @@ void KICAD_NETLIST_PARSER::parseComponent()
|
||||
|
||||
break;
|
||||
|
||||
case T_units:
|
||||
{
|
||||
// Parse a section like:
|
||||
// (units (unit (ref "U1A") (name "A") (pins (pin "1") (pin "2"))))
|
||||
while( ( token = NextTok() ) != T_RIGHT )
|
||||
{
|
||||
if( token == T_LEFT )
|
||||
token = NextTok();
|
||||
|
||||
if( token == T_unit )
|
||||
{
|
||||
COMPONENT::UNIT_INFO info;
|
||||
|
||||
while( ( token = NextTok() ) != T_RIGHT )
|
||||
{
|
||||
if( token == T_LEFT )
|
||||
token = NextTok();
|
||||
|
||||
switch( token )
|
||||
{
|
||||
case T_name:
|
||||
NeedSYMBOLorNUMBER();
|
||||
info.m_unitName = From_UTF8( CurText() );
|
||||
NeedRIGHT();
|
||||
break;
|
||||
|
||||
case T_pins:
|
||||
while( ( token = NextTok() ) != T_RIGHT )
|
||||
{
|
||||
if( token == T_LEFT )
|
||||
token = NextTok();
|
||||
|
||||
if( token == T_pin )
|
||||
{
|
||||
wxString pinNum;
|
||||
|
||||
// Parse pins in attribute style: (pin (num "1"))
|
||||
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
|
||||
{
|
||||
if( token == T_LEFT )
|
||||
token = NextTok();
|
||||
|
||||
if( token == T_num )
|
||||
{
|
||||
NeedSYMBOLorNUMBER();
|
||||
pinNum = From_UTF8( CurText() );
|
||||
NeedRIGHT();
|
||||
}
|
||||
else
|
||||
{
|
||||
// ignore other subfields of pin
|
||||
// leave bare tokens untouched; they are not supported in this context
|
||||
}
|
||||
}
|
||||
|
||||
if( !pinNum.IsEmpty() )
|
||||
info.m_pins.emplace_back( pinNum );
|
||||
}
|
||||
else
|
||||
{
|
||||
skipCurrent();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
skipCurrent();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
parsedUnits.push_back( info );
|
||||
}
|
||||
else
|
||||
{
|
||||
skipCurrent();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case T_component_classes:
|
||||
while( ( token = NextTok() ) != T_RIGHT )
|
||||
{
|
||||
@ -583,6 +666,7 @@ void KICAD_NETLIST_PARSER::parseComponent()
|
||||
component->SetDuplicatePadNumbersAreJumpers( duplicatePinsAreJumpers );
|
||||
std::ranges::copy( jumperPinGroups, std::inserter( component->JumperPadGroups(),
|
||||
component->JumperPadGroups().end() ) );
|
||||
component->SetUnitInfo( parsedUnits );
|
||||
m_netlist->AddComponent( component );
|
||||
}
|
||||
|
||||
|
@ -56,6 +56,14 @@ void COMPONENT::SetFootprint( FOOTPRINT* aFootprint )
|
||||
aFootprint->SetValue( m_value );
|
||||
aFootprint->SetFPID( m_fpid );
|
||||
aFootprint->SetPath( path );
|
||||
|
||||
// Copy over unit info
|
||||
std::vector<FOOTPRINT::FP_UNIT_INFO> fpUnits;
|
||||
|
||||
for( const UNIT_INFO& u : m_units )
|
||||
fpUnits.push_back( { u.m_unitName, u.m_pins } );
|
||||
|
||||
aFootprint->SetUnitInfo( fpUnits );
|
||||
}
|
||||
|
||||
|
||||
|
@ -206,6 +206,16 @@ public:
|
||||
NETLIST_GROUP* GetGroup() const { return m_group; }
|
||||
void SetGroup( NETLIST_GROUP* aGroup ) { m_group = aGroup; }
|
||||
|
||||
// Unit info for multi-unit symbols
|
||||
struct UNIT_INFO
|
||||
{
|
||||
wxString m_unitName; // e.g. A
|
||||
std::vector<wxString> m_pins; // pin numbers in this unit
|
||||
};
|
||||
|
||||
void SetUnitInfo( const std::vector<UNIT_INFO>& aUnits ) { m_units = aUnits; }
|
||||
const std::vector<UNIT_INFO>& GetUnitInfo() const { return m_units; }
|
||||
|
||||
private:
|
||||
std::vector<COMPONENT_NET> m_nets; ///< list of nets shared by the component pins
|
||||
|
||||
@ -260,6 +270,9 @@ private:
|
||||
NETLIST_GROUP* m_group;
|
||||
|
||||
static COMPONENT_NET m_emptyNet;
|
||||
|
||||
// Unit information parsed from the netlist (optional)
|
||||
std::vector<UNIT_INFO> m_units;
|
||||
};
|
||||
|
||||
|
||||
|
@ -109,6 +109,7 @@
|
||||
#include <widgets/pcb_net_inspector_panel.h>
|
||||
#include <widgets/wx_aui_utils.h>
|
||||
#include <kiplatform/app.h>
|
||||
#include <kiplatform/ui.h>
|
||||
#include <core/profile.h>
|
||||
#include <math/box2_minmax.h>
|
||||
#include <view/wx_view_controls.h>
|
||||
@ -1466,7 +1467,7 @@ void PCB_EDIT_FRAME::ActivateGalCanvas()
|
||||
}
|
||||
|
||||
|
||||
void PCB_EDIT_FRAME::ShowBoardSetupDialog( const wxString& aInitialPage )
|
||||
void PCB_EDIT_FRAME::ShowBoardSetupDialog( const wxString& aInitialPage, wxWindow* aParent )
|
||||
{
|
||||
static std::mutex dialogMutex; // Local static mutex
|
||||
|
||||
@ -1486,7 +1487,7 @@ void PCB_EDIT_FRAME::ShowBoardSetupDialog( const wxString& aInitialPage )
|
||||
// Make sure everything's up-to-date
|
||||
GetBoard()->BuildListOfNets();
|
||||
|
||||
DIALOG_BOARD_SETUP dlg( this );
|
||||
DIALOG_BOARD_SETUP dlg( this, aParent );
|
||||
|
||||
if( !aInitialPage.IsEmpty() )
|
||||
dlg.SetInitialPage( aInitialPage, wxEmptyString );
|
||||
|
@ -301,7 +301,7 @@ public:
|
||||
///< @copydoc EDA_DRAW_FRAME::UseGalCanvas()
|
||||
void ActivateGalCanvas() override;
|
||||
|
||||
void ShowBoardSetupDialog( const wxString& aInitialPage = wxEmptyString );
|
||||
void ShowBoardSetupDialog( const wxString& aInitialPage = wxEmptyString, wxWindow* aParent = nullptr );
|
||||
|
||||
void PrepareLayerIndicator( bool aForceRebuild = false );
|
||||
|
||||
|
@ -1238,6 +1238,26 @@ void PCB_IO_KICAD_SEXPR::format( const FOOTPRINT* aFootprint ) const
|
||||
if( !aFootprint->GetSheetfile().empty() )
|
||||
m_out->Print( "(sheetfile %s)", m_out->Quotew( aFootprint->GetSheetfile() ).c_str() );
|
||||
|
||||
// Emit unit info for gate swapping metadata (flat pin list form)
|
||||
if( !aFootprint->GetUnitInfo().empty() )
|
||||
{
|
||||
m_out->Print( "(units" );
|
||||
|
||||
for( const FOOTPRINT::FP_UNIT_INFO& u : aFootprint->GetUnitInfo() )
|
||||
{
|
||||
m_out->Print( "(unit (name %s)", m_out->Quotew( u.m_unitName ).c_str() );
|
||||
m_out->Print( "(pins" );
|
||||
|
||||
for( const wxString& n : u.m_pins )
|
||||
m_out->Print( " %s", m_out->Quotew( n ).c_str() );
|
||||
|
||||
m_out->Print( ")" ); // </pins>
|
||||
m_out->Print( ")" ); // </unit>
|
||||
}
|
||||
|
||||
m_out->Print( ")" ); // </units>
|
||||
}
|
||||
|
||||
if( aFootprint->GetLocalSolderMaskMargin().has_value() )
|
||||
{
|
||||
m_out->Print( "(solder_mask_margin %s)",
|
||||
|
@ -191,7 +191,8 @@ class PCB_IO_KICAD_SEXPR; // forward decl
|
||||
//#define SEXPR_BOARD_FILE_VERSION 20250818 // Support for custom layer counts in footprints
|
||||
//#define SEXPR_BOARD_FILE_VERSION 20250829 // Support Rounded Rectangles
|
||||
//#define SEXPR_BOARD_FILE_VERSION 20250901 // PCB points
|
||||
#define SEXPR_BOARD_FILE_VERSION 20250907 // uuids for tables
|
||||
//#define SEXPR_BOARD_FILE_VERSION 20250907 // uuids for tables
|
||||
#define SEXPR_BOARD_FILE_VERSION 20250909 // footprint unit metadata (units/pins)
|
||||
|
||||
#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
|
||||
|
@ -4758,6 +4758,68 @@ FOOTPRINT* PCB_IO_KICAD_SEXPR_PARSER::parseFOOTPRINT_unchecked( wxArrayString* a
|
||||
NeedRIGHT();
|
||||
break;
|
||||
|
||||
case T_units:
|
||||
{
|
||||
std::vector<FOOTPRINT::FP_UNIT_INFO> unitInfos;
|
||||
|
||||
// (units (unit (name "A") (pins "1" "2" ...)) ...)
|
||||
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
|
||||
{
|
||||
if( token == T_LEFT )
|
||||
token = NextTok();
|
||||
|
||||
if( token == T_unit )
|
||||
{
|
||||
FOOTPRINT::FP_UNIT_INFO info;
|
||||
|
||||
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
|
||||
{
|
||||
if( token == T_LEFT )
|
||||
token = NextTok();
|
||||
|
||||
if( token == T_name )
|
||||
{
|
||||
NeedSYMBOLorNUMBER();
|
||||
info.m_unitName = FromUTF8();
|
||||
NeedRIGHT();
|
||||
}
|
||||
else if( token == T_pins )
|
||||
{
|
||||
// Parse a flat list of quoted numbers or symbols until ')'
|
||||
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
|
||||
{
|
||||
if( token == T_STRING || token == T_NUMBER )
|
||||
{
|
||||
info.m_pins.emplace_back( FromUTF8() );
|
||||
}
|
||||
else
|
||||
{
|
||||
Expecting( "pin number" );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unknown sub-token inside unit; skip its list if any
|
||||
skipCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
unitInfos.push_back( info );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unknown entry under units; skip
|
||||
skipCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
if( !unitInfos.empty() )
|
||||
footprint->SetUnitInfo( unitInfos );
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case T_autoplace_cost90:
|
||||
case T_autoplace_cost180:
|
||||
parseInt( "legacy auto-place cost" );
|
||||
|
@ -52,6 +52,9 @@ enum pcbnew_ids
|
||||
ID_POPUP_PCB_SELECT_VIASIZE14,
|
||||
ID_POPUP_PCB_SELECT_VIASIZE15,
|
||||
ID_POPUP_PCB_SELECT_VIASIZE16,
|
||||
// Dynamic gate swap submenu entries (index offset added to base)
|
||||
ID_POPUP_PCB_SWAP_UNIT_BASE,
|
||||
ID_POPUP_PCB_SWAP_UNIT_LAST = ID_POPUP_PCB_SWAP_UNIT_BASE + 63,
|
||||
ID_POPUP_PCB_SELECT_CUSTOM_DIFFPAIR,
|
||||
ID_POPUP_PCB_SELECT_USE_NETCLASS_DIFFPAIR,
|
||||
ID_POPUP_PCB_SELECT_DIFFPAIR1,
|
||||
|
@ -58,6 +58,7 @@
|
||||
#include <tools/pad_tool.h>
|
||||
#include <view/view_controls.h>
|
||||
#include <connectivity/connectivity_algo.h>
|
||||
#include <pcbnew_id.h>
|
||||
#include <core/kicad_algo.h>
|
||||
#include <fix_board_shape.h>
|
||||
#include <bitmaps.h>
|
||||
@ -208,6 +209,242 @@ static std::shared_ptr<CONDITIONAL_MENU> makeShapeModificationMenu( TOOL_INTERAC
|
||||
return menu;
|
||||
};
|
||||
|
||||
|
||||
// Gate-swap submenu and helpers
|
||||
class GATE_SWAP_MENU : public ACTION_MENU
|
||||
{
|
||||
public:
|
||||
GATE_SWAP_MENU() :
|
||||
ACTION_MENU( true )
|
||||
{
|
||||
SetIcon( BITMAPS::swap );
|
||||
SetTitle( _( "Swap Gate Nets..." ) );
|
||||
}
|
||||
|
||||
|
||||
// We're looking for a selection of pad(s) that belong to a single footprint with multiple units.
|
||||
// Ignore non-pad items since we might have grabbed some traces inside the pad, etc.
|
||||
static const FOOTPRINT* GetSingleEligibleFootprint( const SELECTION& aSelection )
|
||||
{
|
||||
const FOOTPRINT* single = nullptr;
|
||||
|
||||
for( const EDA_ITEM* it : aSelection )
|
||||
{
|
||||
if( it->Type() != PCB_PAD_T )
|
||||
continue;
|
||||
|
||||
const PAD* pad = static_cast<const PAD*>( static_cast<const BOARD_ITEM*>( it ) );
|
||||
const FOOTPRINT* fp = pad->GetParentFootprint();
|
||||
|
||||
if( !fp )
|
||||
continue;
|
||||
|
||||
const auto& units = fp->GetUnitInfo();
|
||||
|
||||
if( units.size() < 2 )
|
||||
continue;
|
||||
|
||||
const wxString& padNum = pad->GetNumber();
|
||||
bool inAnyUnit = false;
|
||||
|
||||
for( const auto& u : units )
|
||||
{
|
||||
for( const auto& pnum : u.m_pins )
|
||||
{
|
||||
if( pnum == padNum )
|
||||
{
|
||||
inAnyUnit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( inAnyUnit )
|
||||
break;
|
||||
}
|
||||
|
||||
if( !inAnyUnit )
|
||||
continue;
|
||||
|
||||
if( !single )
|
||||
single = fp;
|
||||
else if( single != fp )
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return single;
|
||||
}
|
||||
|
||||
|
||||
static std::unordered_set<wxString> CollectSelectedPadNumbers( const SELECTION& aSelection,
|
||||
const FOOTPRINT* aFootprint )
|
||||
{
|
||||
std::unordered_set<wxString> padNums;
|
||||
|
||||
for( const EDA_ITEM* it : aSelection )
|
||||
{
|
||||
if( it->Type() != PCB_PAD_T )
|
||||
continue;
|
||||
|
||||
const PAD* pad = static_cast<const PAD*>( static_cast<const BOARD_ITEM*>( it ) );
|
||||
|
||||
if( pad->GetParentFootprint() != aFootprint )
|
||||
continue;
|
||||
|
||||
padNums.insert( pad->GetNumber() );
|
||||
}
|
||||
|
||||
return padNums;
|
||||
}
|
||||
|
||||
|
||||
// Make a list of the unit names that have any pad selected
|
||||
static std::vector<int> GetUnitsHitIndices( const FOOTPRINT* aFootprint,
|
||||
const std::unordered_set<wxString>& aSelPadNums )
|
||||
{
|
||||
std::vector<int> indices;
|
||||
|
||||
const auto& units = aFootprint->GetUnitInfo();
|
||||
|
||||
for( size_t i = 0; i < units.size(); ++i )
|
||||
{
|
||||
bool hasAny = false;
|
||||
|
||||
for( const auto& pn : units[i].m_pins )
|
||||
{
|
||||
if( aSelPadNums.count( pn ) )
|
||||
{
|
||||
hasAny = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( hasAny )
|
||||
indices.push_back( static_cast<int>( i ) );
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
|
||||
// Gate swapping requires the swapped units to have equal pin counts
|
||||
static bool EqualPinCounts( const FOOTPRINT* aFootprint, const std::vector<int>& aUnitIndices )
|
||||
{
|
||||
if( aUnitIndices.empty() )
|
||||
return false;
|
||||
|
||||
const auto& units = aFootprint->GetUnitInfo();
|
||||
const size_t cnt = units[static_cast<size_t>( aUnitIndices.front() )].m_pins.size();
|
||||
|
||||
for( int idx : aUnitIndices )
|
||||
{
|
||||
if( units[static_cast<size_t>( idx )].m_pins.size() != cnt )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Used when we have exactly one source unit selected; find all other units with equal pin counts
|
||||
static std::vector<int> GetCompatibleTargets( const FOOTPRINT* aFootprint, int aSourceIdx )
|
||||
{
|
||||
std::vector<int> targets;
|
||||
|
||||
const auto& units = aFootprint->GetUnitInfo();
|
||||
const size_t pinCount = units[static_cast<size_t>( aSourceIdx )].m_pins.size();
|
||||
|
||||
for( size_t i = 0; i < units.size(); ++i )
|
||||
{
|
||||
if( static_cast<int>( i ) == aSourceIdx )
|
||||
continue;
|
||||
|
||||
if( units[i].m_pins.size() != pinCount )
|
||||
continue;
|
||||
|
||||
targets.push_back( static_cast<int>( i ) );
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
protected:
|
||||
ACTION_MENU* create() const override { return new GATE_SWAP_MENU(); }
|
||||
|
||||
// The gate swap menu dynamically populates itself based on current selection of pads
|
||||
// on a single multi-unit footprint.
|
||||
//
|
||||
// If there is exactly one unit with any pad selected, we build a menu of available swaps
|
||||
// with all other units with equal pin counts.
|
||||
void update() override
|
||||
{
|
||||
Clear();
|
||||
|
||||
PCB_SELECTION_TOOL* selTool = getToolManager()->GetTool<PCB_SELECTION_TOOL>();
|
||||
const SELECTION& sel = selTool->GetSelection();
|
||||
|
||||
const FOOTPRINT* fp = GetSingleEligibleFootprint( sel );
|
||||
|
||||
if( !fp )
|
||||
return;
|
||||
|
||||
std::unordered_set<wxString> selPadNums = CollectSelectedPadNumbers( sel, fp );
|
||||
|
||||
std::vector<int> unitsHit = GetUnitsHitIndices( fp, selPadNums );
|
||||
|
||||
if( unitsHit.size() != 1 )
|
||||
return;
|
||||
|
||||
const int sourceIdx = unitsHit.front();
|
||||
std::vector<int> targets = GetCompatibleTargets( fp, sourceIdx );
|
||||
|
||||
for( int idx : targets )
|
||||
{
|
||||
wxString label;
|
||||
label.Printf( _( "Swap with %s" ), fp->GetUnitInfo()[static_cast<size_t>( idx )].m_unitName );
|
||||
Append( ID_POPUP_PCB_SWAP_UNIT_BASE + idx, label );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OPT_TOOL_EVENT eventHandler( const wxMenuEvent& aEvent ) override
|
||||
{
|
||||
int id = aEvent.GetId();
|
||||
|
||||
if( id >= ID_POPUP_PCB_SWAP_UNIT_BASE && id <= ID_POPUP_PCB_SWAP_UNIT_LAST )
|
||||
{
|
||||
PCB_SELECTION_TOOL* selTool = getToolManager()->GetTool<PCB_SELECTION_TOOL>();
|
||||
const SELECTION& sel = selTool->GetSelection();
|
||||
|
||||
const FOOTPRINT* fp = GetSingleEligibleFootprint( sel );
|
||||
|
||||
if( !fp )
|
||||
return OPT_TOOL_EVENT();
|
||||
|
||||
const auto& units = fp->GetUnitInfo();
|
||||
const int targetIdx = id - ID_POPUP_PCB_SWAP_UNIT_BASE;
|
||||
|
||||
if( targetIdx < 0 || targetIdx >= static_cast<int>( units.size() ) )
|
||||
return OPT_TOOL_EVENT();
|
||||
|
||||
TOOL_EVENT evt = PCB_ACTIONS::swapGateNets.MakeEvent();
|
||||
evt.SetParameter( units[targetIdx].m_unitName );
|
||||
|
||||
return OPT_TOOL_EVENT( evt );
|
||||
}
|
||||
|
||||
return OPT_TOOL_EVENT();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static std::shared_ptr<ACTION_MENU> makeGateSwapMenu( TOOL_INTERACTIVE* aTool )
|
||||
{
|
||||
auto menu = std::make_shared<GATE_SWAP_MENU>();
|
||||
menu->SetTool( aTool );
|
||||
return menu;
|
||||
};
|
||||
|
||||
|
||||
bool EDIT_TOOL::Init()
|
||||
{
|
||||
// Find the selection tool, so they can cooperate
|
||||
@ -219,6 +456,9 @@ bool EDIT_TOOL::Init()
|
||||
std::shared_ptr<CONDITIONAL_MENU> shapeModificationSubMenu = makeShapeModificationMenu( this );
|
||||
m_selectionTool->GetToolMenu().RegisterSubMenu( shapeModificationSubMenu );
|
||||
|
||||
std::shared_ptr<ACTION_MENU> gateSwapSubMenu = makeGateSwapMenu( this );
|
||||
m_selectionTool->GetToolMenu().RegisterSubMenu( gateSwapSubMenu );
|
||||
|
||||
auto positioningToolsCondition =
|
||||
[this]( const SELECTION& aSel )
|
||||
{
|
||||
@ -235,6 +475,47 @@ bool EDIT_TOOL::Init()
|
||||
return subMenu->GetMenuItemCount() > 0;
|
||||
};
|
||||
|
||||
// Does selection map to a single eligible footprint and exactly one unit?
|
||||
auto gateSwapSingleUnitOnOneFootprint =
|
||||
[]( const SELECTION& aSelection )
|
||||
{
|
||||
const FOOTPRINT* fp = GATE_SWAP_MENU::GetSingleEligibleFootprint( aSelection );
|
||||
|
||||
if( !fp )
|
||||
return false;
|
||||
|
||||
std::unordered_set<wxString> selPadNums = GATE_SWAP_MENU::CollectSelectedPadNumbers( aSelection, fp );
|
||||
|
||||
std::vector<int> unitsHit = GATE_SWAP_MENU::GetUnitsHitIndices( fp, selPadNums );
|
||||
|
||||
if( unitsHit.size() != 1 )
|
||||
return false;
|
||||
|
||||
const int sourceIdx = unitsHit.front();
|
||||
std::vector<int> targets = GATE_SWAP_MENU::GetCompatibleTargets( fp, sourceIdx );
|
||||
return !targets.empty();
|
||||
};
|
||||
|
||||
// Does selection map to a single eligible footprint and more than one unit with equal pin counts?
|
||||
auto gateSwapMultipleUnitsOnOneFootprint =
|
||||
[]( const SELECTION& aSelection )
|
||||
{
|
||||
const FOOTPRINT* fp = GATE_SWAP_MENU::GetSingleEligibleFootprint( aSelection );
|
||||
|
||||
if( !fp )
|
||||
return false;
|
||||
|
||||
std::unordered_set<wxString> selPadNums = GATE_SWAP_MENU::CollectSelectedPadNumbers( aSelection, fp );
|
||||
|
||||
std::vector<int> unitsHit = GATE_SWAP_MENU::GetUnitsHitIndices( fp, selPadNums );
|
||||
|
||||
if( unitsHit.size() < 2 )
|
||||
return false;
|
||||
|
||||
return GATE_SWAP_MENU::EqualPinCounts( fp, unitsHit );
|
||||
};
|
||||
|
||||
|
||||
auto propertiesCondition =
|
||||
[this]( const SELECTION& aSel )
|
||||
{
|
||||
@ -381,6 +662,10 @@ bool EDIT_TOOL::Init()
|
||||
menu.AddItem( PCB_ACTIONS::mirrorH, canMirror );
|
||||
menu.AddItem( PCB_ACTIONS::mirrorV, canMirror );
|
||||
menu.AddItem( PCB_ACTIONS::swap, SELECTION_CONDITIONS::MoreThan( 1 ) );
|
||||
menu.AddItem( PCB_ACTIONS::swapPadNets, SELECTION_CONDITIONS::MoreThan( 1 )
|
||||
&& SELECTION_CONDITIONS::OnlyTypes( padTypes ) );
|
||||
menu.AddItem( PCB_ACTIONS::swapGateNets, gateSwapMultipleUnitsOnOneFootprint );
|
||||
menu.AddMenu( gateSwapSubMenu.get(), gateSwapSingleUnitOnOneFootprint );
|
||||
menu.AddItem( PCB_ACTIONS::packAndMoveFootprints, SELECTION_CONDITIONS::MoreThan( 1 )
|
||||
&& SELECTION_CONDITIONS::HasType( PCB_FOOTPRINT_T ) );
|
||||
|
||||
@ -3532,6 +3817,8 @@ void EDIT_TOOL::setTransitions()
|
||||
Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirrorH.MakeEvent() );
|
||||
Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirrorV.MakeEvent() );
|
||||
Go( &EDIT_TOOL::Swap, PCB_ACTIONS::swap.MakeEvent() );
|
||||
Go( &EDIT_TOOL::SwapPadNets, PCB_ACTIONS::swapPadNets.MakeEvent() );
|
||||
Go( &EDIT_TOOL::SwapGateNets, PCB_ACTIONS::swapGateNets.MakeEvent() );
|
||||
Go( &EDIT_TOOL::PackAndMoveFootprints, PCB_ACTIONS::packAndMoveFootprints.MakeEvent() );
|
||||
Go( &EDIT_TOOL::ChangeTrackWidth, PCB_ACTIONS::changeTrackWidth.MakeEvent() );
|
||||
Go( &EDIT_TOOL::ChangeTrackLayer, PCB_ACTIONS::changeTrackLayerNext.MakeEvent() );
|
||||
|
@ -114,6 +114,14 @@ public:
|
||||
*/
|
||||
int Swap( const TOOL_EVENT& aEvent );
|
||||
|
||||
/**
|
||||
* Swap nets between selected pads and propagate to connected copper items
|
||||
* (tracks, arcs, vias) for unconstrained pin swapping.
|
||||
*/
|
||||
int SwapPadNets( const TOOL_EVENT& aEvent );
|
||||
int SwapGateNets( const TOOL_EVENT& aEvent );
|
||||
|
||||
|
||||
/**
|
||||
* Try to fit selected footprints inside a minimal area and start movement.
|
||||
*/
|
||||
|
@ -25,6 +25,7 @@
|
||||
*/
|
||||
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <kiplatform/ui.h>
|
||||
#include <board.h>
|
||||
@ -50,6 +51,13 @@
|
||||
#include <drc/drc_interactive_courtyard_clearance.h>
|
||||
#include <view/view_controls.h>
|
||||
|
||||
#include <connectivity/connectivity_data.h>
|
||||
#include <wx/richmsgdlg.h>
|
||||
#include <wx/choicdlg.h>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
||||
|
||||
int EDIT_TOOL::Swap( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
@ -154,6 +162,520 @@ int EDIT_TOOL::Swap( const TOOL_EVENT& aEvent )
|
||||
}
|
||||
|
||||
|
||||
int EDIT_TOOL::SwapPadNets( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
if( isRouterActive() )
|
||||
{
|
||||
wxBell();
|
||||
return 0;
|
||||
}
|
||||
|
||||
PCB_SELECTION& selection = m_selectionTool->RequestSelection( &EDIT_TOOL::PadFilter );
|
||||
|
||||
if( selection.Size() < 2 || !selection.OnlyContains( { PCB_PAD_T } ) )
|
||||
return 0;
|
||||
|
||||
// Get selected pads in selection order, because swapping is cyclic and we let the user pick
|
||||
// the rotation order
|
||||
std::vector<EDA_ITEM*> orderedPads = selection.GetItemsSortedBySelectionOrder();
|
||||
std::vector<PAD*> pads;
|
||||
const size_t padsCount = orderedPads.size();
|
||||
|
||||
for( EDA_ITEM* it : orderedPads )
|
||||
pads.push_back( static_cast<PAD*>( static_cast<BOARD_ITEM*>( it ) ) );
|
||||
|
||||
|
||||
// Record original nets and build selected set for quick membership tests
|
||||
std::vector<int> originalNets( padsCount );
|
||||
std::unordered_set<PAD*> selectedPads;
|
||||
|
||||
for( size_t i = 0; i < padsCount; ++i )
|
||||
{
|
||||
originalNets[i] = pads[i]->GetNetCode();
|
||||
selectedPads.insert( pads[i] );
|
||||
}
|
||||
|
||||
// If all nets are the same, nothing to do
|
||||
bool allSame = true;
|
||||
for( size_t i = 1; i < padsCount; ++i )
|
||||
{
|
||||
if( originalNets[i] != originalNets[0] )
|
||||
{
|
||||
allSame = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( allSame )
|
||||
return 0;
|
||||
|
||||
// Desired new nets are a cyclic rotation of original nets (like Swap positions)
|
||||
auto newNetForIndex = [&]( size_t i )
|
||||
{
|
||||
return originalNets[( i + 1 ) % padsCount];
|
||||
};
|
||||
|
||||
// Take an event commit since we will eventually support this while actively routing the board
|
||||
BOARD_COMMIT localCommit( this );
|
||||
BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
|
||||
|
||||
if( !commit )
|
||||
commit = &localCommit;
|
||||
|
||||
// Connectivity to find items connected to each pad
|
||||
std::shared_ptr<CONNECTIVITY_DATA> connectivity = board()->GetConnectivity();
|
||||
|
||||
// Accumulate changes: for each item, assign the resulting new net
|
||||
std::unordered_map<BOARD_CONNECTED_ITEM*, int> itemNewNets;
|
||||
std::vector<PAD*> nonSelectedPadsToChange;
|
||||
|
||||
for( size_t i = 0; i < padsCount; ++i )
|
||||
{
|
||||
PAD* pad = pads[i];
|
||||
int fromNet = originalNets[i];
|
||||
int toNet = newNetForIndex( i );
|
||||
|
||||
// For each connected item, if it matches fromNet, schedule it for toNet
|
||||
for( BOARD_CONNECTED_ITEM* ci : connectivity->GetConnectedItems( pad, 0 ) )
|
||||
{
|
||||
switch( ci->Type() )
|
||||
{
|
||||
case PCB_TRACE_T:
|
||||
case PCB_ARC_T:
|
||||
case PCB_VIA_T:
|
||||
case PCB_PAD_T:
|
||||
break;
|
||||
// Exclude zones, user probably doesn't want to change zone nets
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if( ci->GetNetCode() != fromNet )
|
||||
continue;
|
||||
|
||||
// Track conflicts: if already assigned a different new net, just overwrite (last wins)
|
||||
itemNewNets[ci] = toNet;
|
||||
|
||||
if( ci->Type() == PCB_PAD_T )
|
||||
{
|
||||
PAD* otherPad = static_cast<PAD*>( ci );
|
||||
|
||||
if( !selectedPads.count( otherPad ) )
|
||||
nonSelectedPadsToChange.push_back( otherPad );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prompt if we would modify non-selected pads:
|
||||
// - Ignore Connected Pins (Yes/Enter - default)
|
||||
// - Swap Connected Pins (No)
|
||||
// - Cancel (Esc)
|
||||
bool includeConnectedPads = true;
|
||||
|
||||
if( !nonSelectedPadsToChange.empty() )
|
||||
{
|
||||
// Deduplicate and format a list of affected pads (reference + pad number)
|
||||
std::unordered_set<PAD*> uniquePads( nonSelectedPadsToChange.begin(),
|
||||
nonSelectedPadsToChange.end() );
|
||||
|
||||
wxString msg;
|
||||
msg.Printf( _( "%zu other connected pad(s) will also change nets. How do you want to proceed?" ),
|
||||
uniquePads.size() );
|
||||
|
||||
wxString details;
|
||||
details << _( "Affected pads:" ) << '\n';
|
||||
for( PAD* p : uniquePads )
|
||||
{
|
||||
const FOOTPRINT* fp = p->GetParentFootprint();
|
||||
details << wxS( " • " ) << ( fp ? fp->GetReference() : _( "<no reference designator>" ) )
|
||||
<< wxS( ":" ) << p->GetNumber() << '\n';
|
||||
}
|
||||
|
||||
wxRichMessageDialog dlg( frame(), msg, _( "Swap Pad Nets" ),
|
||||
wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_WARNING );
|
||||
dlg.SetYesNoLabels( _( "Ignore Connected Pins" ), _( "Swap Connected Pins" ) );
|
||||
dlg.SetExtendedMessage( details );
|
||||
|
||||
int ret = dlg.ShowModal();
|
||||
|
||||
if( ret == wxID_CANCEL )
|
||||
return 0;
|
||||
|
||||
// Yes = Ignore, No = Swap
|
||||
includeConnectedPads = ( ret == wxID_NO );
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
// 1) Selected pads get their new nets directly
|
||||
for( size_t i = 0; i < padsCount; ++i )
|
||||
{
|
||||
commit->Modify( pads[i] );
|
||||
pads[i]->SetNetCode( newNetForIndex( i ) );
|
||||
}
|
||||
|
||||
// 2) Connected items propagate, depending on user choice
|
||||
for( const auto& itemNewNet : itemNewNets )
|
||||
{
|
||||
BOARD_CONNECTED_ITEM* item = itemNewNet.first;
|
||||
int newNet = itemNewNet.second;
|
||||
|
||||
if( item->Type() == PCB_PAD_T )
|
||||
{
|
||||
PAD* p = static_cast<PAD*>( item );
|
||||
|
||||
if( selectedPads.count( p ) )
|
||||
continue; // already changed above
|
||||
|
||||
if( !includeConnectedPads )
|
||||
continue; // skip non-selected pads if requested
|
||||
}
|
||||
|
||||
commit->Modify( item );
|
||||
item->SetNetCode( newNet );
|
||||
}
|
||||
|
||||
if( !localCommit.Empty() )
|
||||
localCommit.Push( _( "Swap Pad Nets" ) );
|
||||
|
||||
// Ensure connectivity visuals update
|
||||
rebuildConnectivity();
|
||||
m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int EDIT_TOOL::SwapGateNets( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
if( isRouterActive() )
|
||||
{
|
||||
wxBell();
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto showError = [this]()
|
||||
{
|
||||
frame()->ShowInfoBarError( _( "Gate swapping must be performed on pads within one multi-gate footprint." ) );
|
||||
};
|
||||
|
||||
PCB_SELECTION& selection = m_selectionTool->RequestSelection( &EDIT_TOOL::PadFilter );
|
||||
|
||||
// Get our sanity checks out of the way to clean up later loops
|
||||
FOOTPRINT* targetFp = nullptr;
|
||||
bool fail = false;
|
||||
|
||||
for( EDA_ITEM* it : selection )
|
||||
{
|
||||
// This shouldn't happen due to the filter, but just in case
|
||||
if( it->Type() != PCB_PAD_T )
|
||||
{
|
||||
fail = true;
|
||||
break;
|
||||
}
|
||||
|
||||
FOOTPRINT* fp = static_cast<PAD*>( static_cast<BOARD_ITEM*>( it ) )->GetParentFootprint();
|
||||
|
||||
if( !targetFp )
|
||||
targetFp = fp;
|
||||
else if( fp && targetFp != fp )
|
||||
{
|
||||
fail = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( fail || !targetFp || targetFp->GetUnitInfo().size() < 2 )
|
||||
{
|
||||
showError();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
const auto& units = targetFp->GetUnitInfo();
|
||||
|
||||
// Collect unit hits and ordered unit list based on selection order
|
||||
std::vector<bool> unitHit( units.size(), false );
|
||||
std::vector<int> unitOrder;
|
||||
|
||||
std::vector<EDA_ITEM*> orderedPads = selection.GetItemsSortedBySelectionOrder();
|
||||
|
||||
for( EDA_ITEM* it : orderedPads )
|
||||
{
|
||||
PAD* pad = static_cast<PAD*>( static_cast<BOARD_ITEM*>( it ) );
|
||||
|
||||
const wxString& padNum = pad->GetNumber();
|
||||
int unitIdx = -1;
|
||||
|
||||
for( size_t i = 0; i < units.size(); ++i )
|
||||
{
|
||||
for( const auto& p : units[i].m_pins )
|
||||
{
|
||||
if( p == padNum )
|
||||
{
|
||||
unitIdx = static_cast<int>( i );
|
||||
|
||||
if( !unitHit[i] )
|
||||
unitOrder.push_back( unitIdx );
|
||||
|
||||
unitHit[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( unitIdx >= 0 )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine active units from selection order: 0 -> bail, 1 -> single-unit flow, 2+ -> cycle
|
||||
std::vector<int> activeUnitIdx;
|
||||
int sourceIdx = -1;
|
||||
|
||||
if( unitOrder.size() >= 2 )
|
||||
{
|
||||
activeUnitIdx = unitOrder;
|
||||
sourceIdx = unitOrder.front();
|
||||
}
|
||||
// If we only have one gate selected, we must have a target unit name parameter to proceed
|
||||
else if( unitOrder.size() == 1 && aEvent.HasParameter() )
|
||||
{
|
||||
sourceIdx = unitOrder.front();
|
||||
wxString targetUnitByName = aEvent.Parameter<wxString>();
|
||||
|
||||
int targetIdx = -1;
|
||||
|
||||
for( size_t i = 0; i < units.size(); ++i )
|
||||
{
|
||||
if( static_cast<int>( i ) == sourceIdx )
|
||||
continue;
|
||||
|
||||
if( units[i].m_pins.size() == units[sourceIdx].m_pins.size() && units[i].m_unitName == targetUnitByName )
|
||||
{
|
||||
targetIdx = static_cast<int>( i );
|
||||
}
|
||||
}
|
||||
|
||||
if( targetIdx < 0 )
|
||||
{
|
||||
showError();
|
||||
return 0;
|
||||
}
|
||||
|
||||
activeUnitIdx.push_back( sourceIdx );
|
||||
activeUnitIdx.push_back( targetIdx );
|
||||
}
|
||||
else
|
||||
{
|
||||
showError();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Verify equal pin counts across all active units
|
||||
const size_t pinCount = units[activeUnitIdx.front()].m_pins.size();
|
||||
|
||||
for( int idx : activeUnitIdx )
|
||||
{
|
||||
if( units[idx].m_pins.size() != pinCount )
|
||||
{
|
||||
frame()->ShowInfoBarError( _( "Gate swapping must be performed on gates with equal pin counts." ) );
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Build per-unit pad arrays and net vectors
|
||||
const size_t unitCount = activeUnitIdx.size();
|
||||
std::vector<std::vector<PAD*>> unitPads( unitCount );
|
||||
std::vector<std::vector<int>> unitNets( unitCount );
|
||||
|
||||
for( size_t ui = 0; ui < unitCount; ++ui )
|
||||
{
|
||||
int uidx = activeUnitIdx[ui];
|
||||
const auto& pins = units[uidx].m_pins;
|
||||
|
||||
for( size_t pi = 0; pi < pinCount; ++pi )
|
||||
{
|
||||
PAD* p = targetFp->FindPadByNumber( pins[pi] );
|
||||
|
||||
if( !p )
|
||||
{
|
||||
frame()->ShowInfoBarError( _( "Gate swapping failed: pad in unit missing from footprint." ) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
unitPads[ui].push_back( p );
|
||||
unitNets[ui].push_back( p->GetNetCode() );
|
||||
}
|
||||
}
|
||||
|
||||
// If all unit nets match across positions, nothing to do
|
||||
bool allSame = true;
|
||||
|
||||
for( size_t pi = 0; pi < pinCount && allSame; ++pi )
|
||||
{
|
||||
int refNet = unitNets[0][pi];
|
||||
|
||||
for( size_t ui = 1; ui < unitCount; ++ui )
|
||||
{
|
||||
if( unitNets[ui][pi] != refNet )
|
||||
{
|
||||
allSame = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( allSame )
|
||||
{
|
||||
frame()->ShowInfoBarError( _( "Gate swapping has no effect: all selected gates have identical nets." ) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: someday support swapping while routing and take that commit
|
||||
BOARD_COMMIT localCommit( this );
|
||||
BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
|
||||
|
||||
if( !commit )
|
||||
commit = &localCommit;
|
||||
|
||||
std::shared_ptr<CONNECTIVITY_DATA> connectivity = board()->GetConnectivity();
|
||||
|
||||
// Accumulate changes: item -> new net
|
||||
std::unordered_map<BOARD_CONNECTED_ITEM*, int> itemNewNets;
|
||||
std::vector<PAD*> nonSelectedPadsToChange;
|
||||
|
||||
// Selected pads in the swap (for suppressing re-adding in connected pad handling)
|
||||
std::unordered_set<PAD*> swapPads;
|
||||
|
||||
for( const auto& v : unitPads )
|
||||
swapPads.insert( v.begin(), v.end() );
|
||||
|
||||
// Schedule net swaps for connectivity-attached items
|
||||
auto scheduleForPad = [&]( PAD* pad, int fromNet, int toNet )
|
||||
{
|
||||
for( BOARD_CONNECTED_ITEM* ci : connectivity->GetConnectedItems( pad, 0 ) )
|
||||
{
|
||||
switch( ci->Type() )
|
||||
{
|
||||
case PCB_TRACE_T:
|
||||
case PCB_ARC_T:
|
||||
case PCB_VIA_T:
|
||||
case PCB_PAD_T:
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if( ci->GetNetCode() != fromNet )
|
||||
continue;
|
||||
|
||||
itemNewNets[ ci ] = toNet;
|
||||
|
||||
if( ci->Type() == PCB_PAD_T )
|
||||
{
|
||||
PAD* other = static_cast<PAD*>( ci );
|
||||
|
||||
if( !swapPads.count( other ) )
|
||||
nonSelectedPadsToChange.push_back( other );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// For each position, rotate nets among units forward
|
||||
for( size_t pi = 0; pi < pinCount; ++pi )
|
||||
{
|
||||
for( size_t ui = 0; ui < unitCount; ++ui )
|
||||
{
|
||||
size_t fromIdx = ui;
|
||||
size_t toIdx = ( ui + 1 ) % unitCount;
|
||||
|
||||
PAD* padFrom = unitPads[fromIdx][pi];
|
||||
int fromNet = unitNets[fromIdx][pi];
|
||||
int toNet = unitNets[toIdx][pi];
|
||||
|
||||
scheduleForPad( padFrom, fromNet, toNet );
|
||||
}
|
||||
}
|
||||
|
||||
bool includeConnectedPads = true;
|
||||
|
||||
if( !nonSelectedPadsToChange.empty() )
|
||||
{
|
||||
std::unordered_set<PAD*> uniquePads( nonSelectedPadsToChange.begin(), nonSelectedPadsToChange.end() );
|
||||
|
||||
wxString msg;
|
||||
msg.Printf( _( "%zu other connected pad(s) will also change nets. How do you want to proceed?" ),
|
||||
uniquePads.size() );
|
||||
|
||||
wxString details;
|
||||
details << _( "Affected pads:" ) << '\n';
|
||||
|
||||
for( PAD* p : uniquePads )
|
||||
{
|
||||
const FOOTPRINT* fp = p->GetParentFootprint();
|
||||
details << wxS( " • " ) << ( fp ? fp->GetReference() : _( "<no reference designator>" ) )
|
||||
<< wxS( ":" ) << p->GetNumber() << '\n';
|
||||
}
|
||||
|
||||
wxRichMessageDialog dlg( frame(), msg, _( "Swap Gate Nets" ),
|
||||
wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_WARNING );
|
||||
dlg.SetYesNoLabels( _( "Ignore Connected Pins" ), _( "Swap Connected Pins" ) );
|
||||
dlg.SetExtendedMessage( details );
|
||||
|
||||
int ret = dlg.ShowModal();
|
||||
|
||||
if( ret == wxID_CANCEL )
|
||||
return 0;
|
||||
|
||||
includeConnectedPads = ( ret == wxID_NO );
|
||||
}
|
||||
|
||||
// Apply pad net swaps: rotate per position
|
||||
for( size_t pi = 0; pi < pinCount; ++pi )
|
||||
{
|
||||
// First write back nets for each unit's pad at this position
|
||||
for( size_t ui = 0; ui < unitCount; ++ui )
|
||||
{
|
||||
size_t toIdx = ( ui + 1 ) % unitCount;
|
||||
PAD* pad = unitPads[ui][pi];
|
||||
int newNet = unitNets[toIdx][pi];
|
||||
|
||||
commit->Modify( pad );
|
||||
pad->SetNetCode( newNet );
|
||||
}
|
||||
}
|
||||
|
||||
// Apply connected items
|
||||
for( const auto& kv : itemNewNets )
|
||||
{
|
||||
BOARD_CONNECTED_ITEM* item = kv.first;
|
||||
int newNet = kv.second;
|
||||
|
||||
if( item->Type() == PCB_PAD_T )
|
||||
{
|
||||
PAD* p = static_cast<PAD*>( item );
|
||||
|
||||
if( swapPads.count( p ) )
|
||||
continue;
|
||||
|
||||
if( !includeConnectedPads )
|
||||
continue;
|
||||
}
|
||||
|
||||
commit->Modify( item );
|
||||
item->SetNetCode( newNet );
|
||||
}
|
||||
|
||||
if( !localCommit.Empty() )
|
||||
localCommit.Push( _( "Swap Gate Nets" ) );
|
||||
|
||||
rebuildConnectivity();
|
||||
m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int EDIT_TOOL::PackAndMoveFootprints( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
if( isRouterActive() || m_dragging )
|
||||
@ -868,4 +1390,3 @@ bool EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, BOARD_COMMIT* aCommit
|
||||
|
||||
return !restore_state;
|
||||
}
|
||||
|
||||
|
@ -686,6 +686,21 @@ TOOL_ACTION PCB_ACTIONS::swap( TOOL_ACTION_ARGS()
|
||||
.Tooltip( _( "Swap positions of selected items" ) )
|
||||
.Icon( BITMAPS::swap ) );
|
||||
|
||||
TOOL_ACTION PCB_ACTIONS::swapPadNets( TOOL_ACTION_ARGS()
|
||||
.Name( "pcbnew.InteractiveEdit.swapPadNets" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.FriendlyName( _( "Swap Pad Nets" ) )
|
||||
.Tooltip( _( "Swap nets between two selected pads and their connected copper" ) )
|
||||
.Icon( BITMAPS::swap ) );
|
||||
|
||||
TOOL_ACTION PCB_ACTIONS::swapGateNets( TOOL_ACTION_ARGS()
|
||||
.Name( "pcbnew.InteractiveEdit.swapGateNets" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.FriendlyName( _( "Swap Gate Nets" ) )
|
||||
.Tooltip( _( "Swap nets between gates of a footprint and their connected copper" ) )
|
||||
.Parameter<wxString>( wxString() )
|
||||
.Icon( BITMAPS::swap ) );
|
||||
|
||||
TOOL_ACTION PCB_ACTIONS::packAndMoveFootprints( TOOL_ACTION_ARGS()
|
||||
.Name( "pcbnew.InteractiveEdit.packAndMoveFootprints" )
|
||||
.Scope( AS_GLOBAL )
|
||||
|
@ -128,6 +128,10 @@ public:
|
||||
/// Swapping of selected items
|
||||
static TOOL_ACTION swap;
|
||||
|
||||
/// Swap nets between selected pads/gates (and connected copper)
|
||||
static TOOL_ACTION swapPadNets;
|
||||
static TOOL_ACTION swapGateNets;
|
||||
|
||||
/// Pack and start moving selected footprints
|
||||
static TOOL_ACTION packAndMoveFootprints;
|
||||
|
||||
|
@ -15,20 +15,6 @@
|
||||
// To maximize reusability:
|
||||
// DO NOT ADD CODE THAT REQUIRES C++ EXCEPTION HANDLING.
|
||||
|
||||
// Disable linking to pythonX_d.lib on Windows in debug mode.
|
||||
#if defined(_MSC_VER) && defined(_DEBUG) && !defined(Py_DEBUG)
|
||||
// Workaround for a VS 2022 issue.
|
||||
// See https://github.com/pybind/pybind11/pull/3497 for full context.
|
||||
// NOTE: This workaround knowingly violates the Python.h include order
|
||||
// requirement (see above).
|
||||
# include <yvals.h>
|
||||
# if _MSVC_STL_VERSION >= 143
|
||||
# include <crtdefs.h>
|
||||
# endif
|
||||
# define PYBIND11_DEBUG_MARKER
|
||||
# undef _DEBUG
|
||||
#endif
|
||||
|
||||
// Don't let Python.h #define (v)snprintf as macro because they are implemented
|
||||
// properly in Visual Studio since 2015.
|
||||
#if defined(_MSC_VER)
|
||||
|
Loading…
x
Reference in New Issue
Block a user