Add pad-recombining to FPEditor's Cleanup Graphics.

Fixes https://gitlab.com/kicad/code/kicad/issues/12487
This commit is contained in:
Jeff Young 2022-09-24 17:29:14 +01:00
parent 22cd2a3428
commit 328cc27020
10 changed files with 404 additions and 108 deletions

View File

@ -1,7 +1,7 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 KiCad Developers, see change_log.txt for contributors.
* Copyright (C) 2020-2022 KiCad Developers, see change_log.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -43,20 +43,21 @@ wxString CLEANUP_ITEM::GetErrorText( int aCode, bool aTranslate ) const
switch( aCode )
{
// For cleanup tracks and vias:
case CLEANUP_SHORTING_TRACK: msg = _HKI( "Remove track shorting two nets" ); break;
case CLEANUP_SHORTING_VIA: msg = _HKI( "Remove via shorting two nets" ); break;
case CLEANUP_REDUNDANT_VIA: msg = _HKI( "Remove redundant via" ); break;
case CLEANUP_DUPLICATE_TRACK: msg = _HKI( "Remove duplicate track" ); break;
case CLEANUP_MERGE_TRACKS: msg = _HKI( "Merge co-linear tracks" ); break;
case CLEANUP_DANGLING_TRACK: msg = _HKI( "Remove track not connected at both ends" ); break;
case CLEANUP_DANGLING_VIA: msg = _HKI( "Remove via connected on fewer than two layers" ); break;
case CLEANUP_ZERO_LENGTH_TRACK: msg = _HKI( "Remove zero-length track" ); break;
case CLEANUP_TRACK_IN_PAD: msg = _HKI( "Remove track inside pad" ); break;
case CLEANUP_SHORTING_TRACK: msg = _HKI( "Remove track shorting two nets" ); break;
case CLEANUP_SHORTING_VIA: msg = _HKI( "Remove via shorting two nets" ); break;
case CLEANUP_REDUNDANT_VIA: msg = _HKI( "Remove redundant via" ); break;
case CLEANUP_DUPLICATE_TRACK: msg = _HKI( "Remove duplicate track" ); break;
case CLEANUP_MERGE_TRACKS: msg = _HKI( "Merge co-linear tracks" ); break;
case CLEANUP_DANGLING_TRACK: msg = _HKI( "Remove track not connected at both ends" ); break;
case CLEANUP_DANGLING_VIA: msg = _HKI( "Remove via connected on less than 2 layers" ); break;
case CLEANUP_ZERO_LENGTH_TRACK: msg = _HKI( "Remove zero-length track" ); break;
case CLEANUP_TRACK_IN_PAD: msg = _HKI( "Remove track inside pad" ); break;
// For cleanup graphics:
case CLEANUP_NULL_GRAPHIC: msg = _HKI( "Remove zero-size graphic" ); break;
case CLEANUP_DUPLICATE_GRAPHIC: msg = _HKI( "Remove duplicated graphic" ); break;
case CLEANUP_LINES_TO_RECT: msg = _HKI( "Convert lines to rectangle" ); break;
case CLEANUP_NULL_GRAPHIC: msg = _HKI( "Remove zero-size graphic" ); break;
case CLEANUP_DUPLICATE_GRAPHIC: msg = _HKI( "Remove duplicated graphic" ); break;
case CLEANUP_LINES_TO_RECT: msg = _HKI( "Convert lines to rectangle" ); break;
case CLEANUP_MERGE_PAD: msg = _HKI( "Merge overlapping shapes into pad" ); break;
default:
wxFAIL_MSG( wxT( "Missing cleanup item description" ) );

View File

@ -1,7 +1,7 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020-2021 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2020-2022 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -42,7 +42,8 @@ enum CLEANUP_RC_CODE {
CLEANUP_TRACK_IN_PAD,
CLEANUP_NULL_GRAPHIC,
CLEANUP_DUPLICATE_GRAPHIC,
CLEANUP_LINES_TO_RECT
CLEANUP_LINES_TO_RECT,
CLEANUP_MERGE_PAD
};
class CLEANUP_ITEM : public RC_ITEM

View File

@ -24,6 +24,7 @@
#include <dialog_cleanup_graphics.h>
#include <board_commit.h>
#include <footprint.h>
#include <pad.h>
#include <tool/tool_manager.h>
#include <tools/pcb_actions.h>
#include <graphics_cleaner.h>
@ -40,9 +41,16 @@ DIALOG_CLEANUP_GRAPHICS::DIALOG_CLEANUP_GRAPHICS( PCB_BASE_FRAME* aParent,
m_changesDataView->AssociateModel( m_changesTreeModel );
if( aIsFootprintEditor )
{
SetupStandardButtons( { { wxID_OK, _( "Update Footprint" ) } } );
m_nettieHint->SetFont( KIUI::GetInfoFont( aParent ).Italic() );
}
else
{
SetupStandardButtons( { { wxID_OK, _( "Update PCB" ) } } );
m_mergePadsOpt->Show( false );
m_nettieHint->Show( false );
}
GetSizer()->SetSizeHints(this);
Centre();
@ -84,7 +92,8 @@ void DIALOG_CLEANUP_GRAPHICS::doCleanup( bool aDryRun )
BOARD_COMMIT commit( m_parentFrame );
BOARD* board = m_parentFrame->GetBoard();
FOOTPRINT* fp = m_isFootprintEditor ? board->GetFirstFootprint() : nullptr;
GRAPHICS_CLEANER cleaner( fp ? fp->GraphicalItems() : board->Drawings(), fp, commit );
GRAPHICS_CLEANER cleaner( fp ? fp->GraphicalItems() : board->Drawings(), fp, commit,
m_parentFrame->GetToolManager() );
if( !aDryRun )
{
@ -101,7 +110,8 @@ void DIALOG_CLEANUP_GRAPHICS::doCleanup( bool aDryRun )
m_parentFrame->Compile_Ratsnest( false );
cleaner.CleanupBoard( aDryRun, &m_items, m_createRectanglesOpt->GetValue(),
m_deleteRedundantOpt->GetValue() );
m_deleteRedundantOpt->GetValue(),
m_mergePadsOpt->GetValue() );
if( aDryRun )
{
@ -123,6 +133,9 @@ void DIALOG_CLEANUP_GRAPHICS::OnSelectItem( wxDataViewEvent& aEvent )
BOARD_ITEM* item = m_parentFrame->GetBoard()->GetItem( itemID );
WINDOW_THAWER thawer( m_parentFrame );
if( item && !item->GetLayerSet().test( m_parentFrame->GetActiveLayer() ) )
m_parentFrame->SetActiveLayer( item->GetLayerSet().UIOrder().front() );
m_parentFrame->FocusOnItem( item );
m_parentFrame->GetCanvas()->Refresh();

View File

@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
@ -23,7 +23,20 @@ DIALOG_CLEANUP_GRAPHICS_BASE::DIALOG_CLEANUP_GRAPHICS_BASE( wxWindow* parent, wx
bSizerUpper->Add( m_createRectanglesOpt, 0, wxALL, 5 );
m_deleteRedundantOpt = new wxCheckBox( this, wxID_ANY, _("Delete redundant graphics"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerUpper->Add( m_deleteRedundantOpt, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 );
bSizerUpper->Add( m_deleteRedundantOpt, 0, wxALL, 5 );
m_mergePadsOpt = new wxCheckBox( this, wxID_ANY, _("Merge overlapping graphics into pads"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerUpper->Add( m_mergePadsOpt, 0, wxTOP|wxRIGHT|wxLEFT, 5 );
wxBoxSizer* bSizerMargins;
bSizerMargins = new wxBoxSizer( wxVERTICAL );
m_nettieHint = new wxStaticText( this, wxID_ANY, _("(Pads which appear in a Net Tie pad group will not be considered for merging.)"), wxDefaultPosition, wxDefaultSize, 0 );
m_nettieHint->Wrap( -1 );
bSizerMargins->Add( m_nettieHint, 0, wxLEFT, 25 );
bSizerUpper->Add( bSizerMargins, 1, wxEXPAND|wxALL, 3 );
bSizerMain->Add( bSizerUpper, 0, wxEXPAND|wxALL, 5 );
@ -40,7 +53,7 @@ DIALOG_CLEANUP_GRAPHICS_BASE::DIALOG_CLEANUP_GRAPHICS_BASE( wxWindow* parent, wx
bLowerSizer->Add( m_changesDataView, 1, wxALL|wxEXPAND, 5 );
bSizerMain->Add( bLowerSizer, 1, wxEXPAND|wxRIGHT|wxLEFT, 5 );
bSizerMain->Add( bLowerSizer, 1, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 );
m_sdbSizer = new wxStdDialogButtonSizer();
m_sdbSizerOK = new wxButton( this, wxID_OK );
@ -61,6 +74,7 @@ DIALOG_CLEANUP_GRAPHICS_BASE::DIALOG_CLEANUP_GRAPHICS_BASE( wxWindow* parent, wx
// Connect Events
m_createRectanglesOpt->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_CLEANUP_GRAPHICS_BASE::OnCheckBox ), NULL, this );
m_deleteRedundantOpt->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_CLEANUP_GRAPHICS_BASE::OnCheckBox ), NULL, this );
m_mergePadsOpt->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_CLEANUP_GRAPHICS_BASE::OnCheckBox ), NULL, this );
m_changesDataView->Connect( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, wxDataViewEventHandler( DIALOG_CLEANUP_GRAPHICS_BASE::OnSelectItem ), NULL, this );
m_changesDataView->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( DIALOG_CLEANUP_GRAPHICS_BASE::OnLeftDClickItem ), NULL, this );
}
@ -70,6 +84,7 @@ DIALOG_CLEANUP_GRAPHICS_BASE::~DIALOG_CLEANUP_GRAPHICS_BASE()
// Disconnect Events
m_createRectanglesOpt->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_CLEANUP_GRAPHICS_BASE::OnCheckBox ), NULL, this );
m_deleteRedundantOpt->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_CLEANUP_GRAPHICS_BASE::OnCheckBox ), NULL, this );
m_mergePadsOpt->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_CLEANUP_GRAPHICS_BASE::OnCheckBox ), NULL, this );
m_changesDataView->Disconnect( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, wxDataViewEventHandler( DIALOG_CLEANUP_GRAPHICS_BASE::OnSelectItem ), NULL, this );
m_changesDataView->Disconnect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( DIALOG_CLEANUP_GRAPHICS_BASE::OnLeftDClickItem ), NULL, this );

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<wxFormBuilder_Project>
<FileVersion major="1" minor="15" />
<FileVersion major="1" minor="16" />
<object class="Project" expanded="1">
<property name="class_decoration"></property>
<property name="code_generation">C++</property>
@ -14,6 +14,7 @@
<property name="file">dialog_cleanup_graphics_base</property>
<property name="first_id">1000</property>
<property name="help_provider">none</property>
<property name="image_path_wrapper_function_name"></property>
<property name="indent_with_spaces"></property>
<property name="internationalize">1</property>
<property name="name">dialog_cleanup_graphics</property>
@ -25,6 +26,7 @@
<property name="skip_php_events">1</property>
<property name="skip_python_events">1</property>
<property name="ui_table">UI</property>
<property name="use_array_enum">0</property>
<property name="use_enum">0</property>
<property name="use_microsoft_bom">0</property>
<object class="Dialog" expanded="1">
@ -50,6 +52,7 @@
<property name="subclass">DIALOG_SHIM; dialog_shim.h</property>
<property name="title">Cleanup Graphics</property>
<property name="tooltip"></property>
<property name="two_step_creation">0</property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
@ -134,7 +137,7 @@
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxBOTTOM|wxRIGHT|wxLEFT</property>
<property name="flag">wxALL</property>
<property name="proportion">0</property>
<object class="wxCheckBox" expanded="1">
<property name="BottomDockable">1</property>
@ -197,11 +200,148 @@
<event name="OnCheckBox">OnCheckBox</event>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxTOP|wxRIGHT|wxLEFT</property>
<property name="proportion">0</property>
<object class="wxCheckBox" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="checked">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Merge overlapping graphics into pads</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_mergePadsOpt</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnCheckBox">OnCheckBox</event>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">3</property>
<property name="flag">wxEXPAND|wxALL</property>
<property name="proportion">1</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizerMargins</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">25</property>
<property name="flag">wxLEFT</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">(Pads which appear in a Net Tie pad group will not be considered for merging.)</property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_nettieHint</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
</object>
</object>
</object>
</object>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND|wxRIGHT|wxLEFT</property>
<property name="flag">wxEXPAND|wxTOP|wxRIGHT|wxLEFT</property>
<property name="proportion">1</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size">660,250</property>

View File

@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
@ -17,8 +17,8 @@
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/sizer.h>
#include <wx/dataview.h>
#include <wx/button.h>
#include <wx/dialog.h>
@ -36,13 +36,15 @@ class DIALOG_CLEANUP_GRAPHICS_BASE : public DIALOG_SHIM
protected:
wxCheckBox* m_createRectanglesOpt;
wxCheckBox* m_deleteRedundantOpt;
wxCheckBox* m_mergePadsOpt;
wxStaticText* m_nettieHint;
wxStaticText* staticChangesLabel;
wxDataViewCtrl* m_changesDataView;
wxStdDialogButtonSizer* m_sdbSizer;
wxButton* m_sdbSizerOK;
wxButton* m_sdbSizerCancel;
// Virtual event handlers, overide them in your derived class
// Virtual event handlers, override them in your derived class
virtual void OnCheckBox( wxCommandEvent& event ) { event.Skip(); }
virtual void OnSelectItem( wxDataViewEvent& event ) { event.Skip(); }
virtual void OnLeftDClickItem( wxMouseEvent& event ) { event.Skip(); }
@ -51,6 +53,7 @@ class DIALOG_CLEANUP_GRAPHICS_BASE : public DIALOG_SHIM
public:
DIALOG_CLEANUP_GRAPHICS_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Cleanup Graphics"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER );
~DIALOG_CLEANUP_GRAPHICS_BASE();
};

View File

@ -29,15 +29,19 @@
#include <cleanup_item.h>
#include <pcb_shape.h>
#include <fp_shape.h>
#include <pad.h>
#include <footprint.h>
#include <graphics_cleaner.h>
#include <board_design_settings.h>
#include <tool/tool_manager.h>
#include <tools/pad_tool.h>
GRAPHICS_CLEANER::GRAPHICS_CLEANER( DRAWINGS& aDrawings, FOOTPRINT* aParentFootprint,
BOARD_COMMIT& aCommit ) :
BOARD_COMMIT& aCommit, TOOL_MANAGER* aToolMgr ) :
m_drawings( aDrawings ),
m_parentFootprint( aParentFootprint ),
m_commit( aCommit ),
m_toolMgr( aToolMgr ),
m_dryRun( true ),
m_epsilon( 0 ),
m_itemsList( nullptr )
@ -47,7 +51,7 @@ GRAPHICS_CLEANER::GRAPHICS_CLEANER( DRAWINGS& aDrawings, FOOTPRINT* aParentFootp
void GRAPHICS_CLEANER::CleanupBoard( bool aDryRun,
std::vector<std::shared_ptr<CLEANUP_ITEM>>* aItemsList,
bool aMergeRects, bool aDeleteRedundant )
bool aMergeRects, bool aDeleteRedundant, bool aMergePads )
{
m_dryRun = aDryRun;
m_itemsList = aItemsList;
@ -64,6 +68,9 @@ void GRAPHICS_CLEANER::CleanupBoard( bool aDryRun,
if( aMergeRects )
mergeRects();
if( aMergePads )
mergePads();
// Clear the flag used to mark some shapes:
for( BOARD_ITEM* drawing : m_drawings )
drawing->ClearFlags( IS_DELETED );
@ -339,3 +346,49 @@ void GRAPHICS_CLEANER::mergeRects()
delete side;
}
void GRAPHICS_CLEANER::mergePads()
{
wxCHECK_MSG( m_parentFootprint, /*void*/, wxT( "mergePads() is FootprintEditor only" ) );
PAD_TOOL* padTool = m_toolMgr->GetTool<PAD_TOOL>();
for( PAD* pad : m_parentFootprint->Pads() )
pad->SetFlags( CANDIDATE );
if( m_parentFootprint->IsNetTie() )
{
for( const wxString& group : m_parentFootprint->GetNetTiePadGroups() )
{
wxStringTokenizer groupParser( group, "," );
while( groupParser.HasMoreTokens() )
{
wxString number = groupParser.GetNextToken().Trim( false ).Trim( true );
if( PAD* pad = m_parentFootprint->FindPadByNumber( number ) )
pad->ClearFlags( CANDIDATE );
}
}
}
for( PAD* pad : m_parentFootprint->Pads() )
{
if( !( pad->GetFlags() & CANDIDATE ) )
continue;
std::vector<FP_SHAPE*> shapes = padTool->RecombinePad( pad, m_dryRun, m_commit );
if( !shapes.empty() )
{
std::shared_ptr<CLEANUP_ITEM> item = std::make_shared<CLEANUP_ITEM>( CLEANUP_MERGE_PAD );
for( FP_SHAPE* shape : shapes )
item->AddItem( shape );
item->AddItem( pad );
m_itemsList->push_back( item );
}
}
}

View File

@ -29,21 +29,25 @@
class FOOTPRINT;
class BOARD_COMMIT;
class CLEANUP_ITEM;
class TOOL_MANAGER;
// Helper class used to clean tracks and vias
class GRAPHICS_CLEANER
{
public:
GRAPHICS_CLEANER( DRAWINGS& aDrawings, FOOTPRINT* aParentFootprint, BOARD_COMMIT& aCommit );
GRAPHICS_CLEANER( DRAWINGS& aDrawings, FOOTPRINT* aParentFootprint, BOARD_COMMIT& aCommit,
TOOL_MANAGER* aToolManager );
/**
* the cleanup function.
* @param aMergeRects = merge for segments forming a rectangle into a rect
* @param aDeleteRedundant = true to delete null graphics and duplicated graphics
* @param aMergePads = true to apply Pad Editor's merge algorithm to all pads in footprint
* (it is assumed this will only be run on FPEditor boards)
*/
void CleanupBoard( bool aDryRun, std::vector<std::shared_ptr<CLEANUP_ITEM> >* aItemsList,
bool aMergeRects, bool aDeleteRedundant );
bool aMergeRects, bool aDeleteRedundant, bool aMergePads );
private:
bool isNullShape( PCB_SHAPE* aShape );
@ -51,11 +55,13 @@ private:
void cleanupShapes();
void mergeRects();
void mergePads();
private:
DRAWINGS& m_drawings;
FOOTPRINT* m_parentFootprint; // nullptr if not in Footprint Editor
BOARD_COMMIT& m_commit;
TOOL_MANAGER* m_toolMgr;
bool m_dryRun;
int m_epsilon;

View File

@ -577,7 +577,11 @@ int PAD_TOOL::EditPad( const TOOL_EVENT& aEvent )
PAD* pad = dynamic_cast<PAD*>( frame()->GetItem( m_editPad ) );
if( pad )
recombinePad( pad );
{
BOARD_COMMIT commit( frame() );
RecombinePad( pad, false, commit );
commit.Push( _( "Recombine pad" ) );
}
m_editPad = niluuid;
}
@ -698,14 +702,18 @@ PCB_LAYER_ID PAD_TOOL::explodePad( PAD* aPad )
}
void PAD_TOOL::recombinePad( PAD* aPad )
std::vector<FP_SHAPE*> PAD_TOOL::RecombinePad( PAD* aPad, bool aIsDryRun, BOARD_COMMIT& aCommit )
{
int maxError = board()->GetDesignSettings().m_MaxError;
int maxError = board()->GetDesignSettings().m_MaxError;
FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aPad->GetParentFootprint() );
// Don't leave an object in the point editor that might no longer exist after
// recombining the pad.
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
for( BOARD_ITEM* item : footprint->GraphicalItems() )
item->ClearFlags( SKIP_STRUCT );
auto findNext =
[&]( PCB_LAYER_ID aLayer ) -> FP_SHAPE*
{
@ -713,11 +721,11 @@ void PAD_TOOL::recombinePad( PAD* aPad )
aPad->TransformShapeWithClearanceToPolygon( padPoly, aLayer, 0, maxError,
ERROR_INSIDE );
for( BOARD_ITEM* item : board()->GetFirstFootprint()->GraphicalItems() )
for( BOARD_ITEM* item : footprint->GraphicalItems() )
{
PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
FP_SHAPE* shape = dynamic_cast<FP_SHAPE*>( item );
if( !shape || ( shape->GetEditFlags() & STRUCT_DELETED ) )
if( !shape || ( shape->GetFlags() & SKIP_STRUCT ) )
continue;
if( shape->GetLayer() != aLayer )
@ -738,8 +746,30 @@ void PAD_TOOL::recombinePad( PAD* aPad )
return nullptr;
};
BOARD_COMMIT commit( frame() );
PCB_LAYER_ID layer;
auto findMatching =
[&]( FP_SHAPE* aShape ) -> std::vector<FP_SHAPE*>
{
std::vector<FP_SHAPE*> matching;
for( BOARD_ITEM* item : footprint->GraphicalItems() )
{
FP_SHAPE* other = dynamic_cast<FP_SHAPE*>( item );
if( !other || ( other->GetFlags() & SKIP_STRUCT ) )
continue;
if( aPad->GetLayerSet().test( other->GetLayer() )
&& aShape->Compare( other ) == 0 )
{
matching.push_back( other );
}
}
return matching;
};
PCB_LAYER_ID layer;
std::vector<FP_SHAPE*> mergedShapes;
if( aPad->IsOnLayer( F_Cu ) )
layer = F_Cu;
@ -750,92 +780,116 @@ void PAD_TOOL::recombinePad( PAD* aPad )
while( FP_SHAPE* fpShape = findNext( layer ) )
{
commit.Modify( aPad );
// We've found an intersecting item. First convert the pad to a custom-shape
// pad (if it isn't already)
// We've found an intersecting item to combine.
//
if( aPad->GetShape() == PAD_SHAPE::RECT || aPad->GetShape() == PAD_SHAPE::CIRCLE )
fpShape->SetFlags( SKIP_STRUCT );
// First convert the pad to a custom-shape pad (if it isn't already)
//
if( !aIsDryRun )
{
aPad->SetAnchorPadShape( aPad->GetShape() );
aCommit.Modify( aPad );
if( aPad->GetShape() == PAD_SHAPE::RECT || aPad->GetShape() == PAD_SHAPE::CIRCLE )
{
aPad->SetAnchorPadShape( aPad->GetShape() );
}
else if( aPad->GetShape() != PAD_SHAPE::CUSTOM )
{
// Create a new minimally-sized circular anchor and convert existing pad
// to a polygon primitive
SHAPE_POLY_SET existingOutline;
aPad->TransformShapeWithClearanceToPolygon( existingOutline, layer, 0, maxError,
ERROR_INSIDE );
aPad->SetAnchorPadShape( PAD_SHAPE::CIRCLE );
if( aPad->GetSizeX() > aPad->GetSizeY() )
aPad->SetSizeX( aPad->GetSizeY() );
aPad->SetOffset( VECTOR2I( 0, 0 ) );
PCB_SHAPE* shape = new PCB_SHAPE( nullptr, SHAPE_T::POLY );
shape->SetFilled( true );
shape->SetStroke( STROKE_PARAMS( 0, PLOT_DASH_TYPE::SOLID ) );
shape->SetPolyShape( existingOutline );
shape->Move( - aPad->GetPosition() );
shape->Rotate( VECTOR2I( 0, 0 ), - aPad->GetOrientation() );
aPad->AddPrimitive( shape );
}
aPad->SetShape( PAD_SHAPE::CUSTOM );
}
else if( aPad->GetShape() != PAD_SHAPE::CUSTOM )
{
// Create a new minimally-sized circular anchor and convert existing pad
// to a polygon primitive
SHAPE_POLY_SET existingOutline;
aPad->TransformShapeWithClearanceToPolygon( existingOutline, layer, 0, maxError,
ERROR_INSIDE );
aPad->SetAnchorPadShape( PAD_SHAPE::CIRCLE );
if( aPad->GetSizeX() > aPad->GetSizeY() )
aPad->SetSizeX( aPad->GetSizeY() );
aPad->SetOffset( VECTOR2I( 0, 0 ) );
PCB_SHAPE* shape = new PCB_SHAPE( nullptr, SHAPE_T::POLY );
shape->SetFilled( true );
shape->SetStroke( STROKE_PARAMS( 0, PLOT_DASH_TYPE::SOLID ) );
shape->SetPolyShape( existingOutline );
shape->Move( - aPad->GetPosition() );
shape->Rotate( VECTOR2I( 0, 0 ), - aPad->GetOrientation() );
aPad->AddPrimitive( shape );
}
aPad->SetShape( PAD_SHAPE::CUSTOM );
// Now add the new shape to the primitives list
//
PCB_SHAPE* pcbShape = new PCB_SHAPE;
mergedShapes.push_back( fpShape );
pcbShape->SetShape( fpShape->GetShape() );
pcbShape->SetFilled( fpShape->IsFilled() );
pcbShape->SetStroke( fpShape->GetStroke() );
switch( pcbShape->GetShape() )
if( !aIsDryRun )
{
case SHAPE_T::SEGMENT:
case SHAPE_T::RECT:
case SHAPE_T::CIRCLE:
pcbShape->SetStart( fpShape->GetStart() );
pcbShape->SetEnd( fpShape->GetEnd() );
break;
PCB_SHAPE* pcbShape = new PCB_SHAPE;
case SHAPE_T::ARC:
pcbShape->SetStart( fpShape->GetStart() );
pcbShape->SetEnd( fpShape->GetEnd() );
pcbShape->SetCenter( fpShape->GetCenter() );
break;
pcbShape->SetShape( fpShape->GetShape() );
pcbShape->SetFilled( fpShape->IsFilled() );
pcbShape->SetStroke( fpShape->GetStroke() );
case SHAPE_T::BEZIER:
pcbShape->SetStart( fpShape->GetStart() );
pcbShape->SetEnd( fpShape->GetEnd() );
pcbShape->SetBezierC1( fpShape->GetBezierC1() );
pcbShape->SetBezierC2( fpShape->GetBezierC2() );
break;
switch( pcbShape->GetShape() )
{
case SHAPE_T::SEGMENT:
case SHAPE_T::RECT:
case SHAPE_T::CIRCLE:
pcbShape->SetStart( fpShape->GetStart() );
pcbShape->SetEnd( fpShape->GetEnd() );
break;
case SHAPE_T::POLY:
pcbShape->SetPolyShape( fpShape->GetPolyShape() );
break;
case SHAPE_T::ARC:
pcbShape->SetStart( fpShape->GetStart() );
pcbShape->SetEnd( fpShape->GetEnd() );
pcbShape->SetCenter( fpShape->GetCenter() );
break;
default:
UNIMPLEMENTED_FOR( pcbShape->SHAPE_T_asString() );
case SHAPE_T::BEZIER:
pcbShape->SetStart( fpShape->GetStart() );
pcbShape->SetEnd( fpShape->GetEnd() );
pcbShape->SetBezierC1( fpShape->GetBezierC1() );
pcbShape->SetBezierC2( fpShape->GetBezierC2() );
break;
case SHAPE_T::POLY:
pcbShape->SetPolyShape( fpShape->GetPolyShape() );
break;
default:
UNIMPLEMENTED_FOR( pcbShape->SHAPE_T_asString() );
}
pcbShape->Move( - aPad->GetPosition() );
pcbShape->Rotate( VECTOR2I( 0, 0 ), - aPad->GetOrientation() );
pcbShape->SetIsAnnotationProxy( fpShape->IsAnnotationProxy());
aPad->AddPrimitive( pcbShape );
aCommit.Remove( fpShape );
}
pcbShape->Move( - aPad->GetPosition() );
pcbShape->Rotate( VECTOR2I( 0, 0 ), - aPad->GetOrientation() );
pcbShape->SetIsAnnotationProxy( fpShape->IsAnnotationProxy());
aPad->AddPrimitive( pcbShape );
// See if there are other shapes that match and mark them for delete. (KiCad won't
// produce these, but old footprints from other vendors have them.)
for( FP_SHAPE* other : findMatching( fpShape ) )
{
other->SetFlags( SKIP_STRUCT );
mergedShapes.push_back( other );
fpShape->SetFlags( STRUCT_DELETED );
commit.Remove( fpShape );
if( !aIsDryRun )
aCommit.Remove( other );
}
}
aPad->ClearFlags( ENTERED );
for( BOARD_ITEM* item : footprint->GraphicalItems() )
item->ClearFlags( SKIP_STRUCT );
commit.Push( _( "Recombine pads" ) );
if( !aIsDryRun )
aPad->ClearFlags( ENTERED );
return mergedShapes;
}

View File

@ -28,6 +28,7 @@
#include <tools/pcb_tool_base.h>
class ACTION_MENU;
class FP_SHAPE;
/**
* Tool relating to pads and pad settings.
@ -64,6 +65,15 @@ public:
wxString GetLastPadNumber() const { return m_lastPadNumber; }
void SetLastPadNumber( const wxString& aPadNumber ) { m_lastPadNumber = aPadNumber; }
/**
* Recombine an exploded pad (or one produced with overlapping polygons in an older version).
* @param aPad the pad to run the recombination algorithm on
* @param aIsDryRun if true the list will be generated but no changes will be made
* @param aCommit the commit to add any changes to
* @return a list of FP_SHAPEs that will be combined
*/
std::vector<FP_SHAPE*> RecombinePad( PAD* aPad, bool aIsDryRun, BOARD_COMMIT& aCommit );
private:
///< Bind handlers to corresponding TOOL_ACTIONs.
void setTransitions() override;
@ -78,8 +88,8 @@ private:
int pushPadSettings( const TOOL_EVENT& aEvent );
PCB_LAYER_ID explodePad( PAD* aPad );
void recombinePad( PAD* aPad );
private:
wxString m_lastPadNumber;
bool m_wasHighContrast;