kicad/pcbnew/dialogs/dialog_global_edit_teardrop...

549 lines
19 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 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
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <widgets/unit_binder.h>
#include <pcb_edit_frame.h>
#include <board.h>
#include <board_design_settings.h>
#include <pcb_track.h>
#include <pcb_group.h>
#include <footprint.h>
#include <teardrop/teardrop.h>
#include <zone_filler.h>
#include <connectivity/connectivity_data.h>
#include <pcb_layer_box_selector.h>
#include <tool/tool_manager.h>
#include <tools/global_edit_tool.h>
#include "dialog_global_edit_teardrops_base.h"
// Globals to remember control settings during a session
static bool g_vias = true;
static bool g_pthPads = true;
static bool g_smdPads = true;
static bool g_trackToTrack = false;
static bool g_filterByNetclass;
static wxString g_netclassFilter;
static bool g_filterByNet;
static wxString g_netFilter;
static bool g_filterByLayer;
static int g_layerFilter;
static bool g_filterRoundPads = false;
static bool g_filterSelected = false;
static int g_action = 1;
class DIALOG_GLOBAL_EDIT_TEARDROPS : public DIALOG_GLOBAL_EDIT_TEARDROPS_BASE
{
public:
DIALOG_GLOBAL_EDIT_TEARDROPS( PCB_EDIT_FRAME* aParent );
~DIALOG_GLOBAL_EDIT_TEARDROPS() override;
protected:
void onSpecifiedValuesUpdateUi( wxUpdateUIEvent& event ) override
{
event.Enable( m_specifiedValues->GetValue() );
}
void onCurvedEdgesUpdateUi( wxUpdateUIEvent& event ) override
{
event.Enable( m_specifiedValues->GetValue()
&& m_curvedEdges->GetValue() == wxCHK_CHECKED );
}
void onFilterUpdateUi( wxUpdateUIEvent& event ) override
{
event.Enable( !m_trackToTrack->GetValue() );
}
// Track-to-track teardrops always follow the document-wide settings (there are no teardrop
// properties on individual track segments as they're too ephemeral). Therefore we disable
// Set-to-specified-values when track-to-track is selected.
void onTrackToTrack( wxCommandEvent& event ) override
{
if( event.IsChecked() && m_specifiedValues->GetValue() )
{
m_specifiedValues->SetValue( false );
m_addTeardrops->SetValue( true );
}
}
// These just improve usability so that you don't have to click twice to enable a filter.
void OnNetclassFilterSelect( wxCommandEvent& event ) override
{
m_netclassFilterOpt->SetValue( true );
}
void OnLayerFilterSelect( wxCommandEvent& event ) override
{
m_layerFilterOpt->SetValue( true );
}
void OnNetFilterSelect( wxCommandEvent& event )
{
m_netFilterOpt->SetValue( true );
}
// Remove "add" terminology when updating only existing teardrops.
void OnExistingFilterSelect( wxCommandEvent& event ) override
{
if( event.IsChecked() )
{
m_addTeardrops->SetLabel( _( "Set teardrops to default values for shape" ) );
m_specifiedValues->SetLabel( _( "Set teardrops to specified values:" ) );
}
else
{
m_addTeardrops->SetLabel( _( "Add teardrops with default values for shape" ) );
m_specifiedValues->SetLabel( _( "Add teardrops with specified values:" ) );
}
}
void setSpecifiedParams( TEARDROP_PARAMETERS* targetParams );
void visitItem( BOARD_COMMIT* aCommit, BOARD_CONNECTED_ITEM* aItem );
void processItem( BOARD_COMMIT* aCommit, BOARD_CONNECTED_ITEM* aItem );
bool TransferDataToWindow() override;
bool TransferDataFromWindow() override;
void onShowBoardSetup( wxHyperlinkEvent& event ) override
{
m_parent->ShowBoardSetupDialog( _( "Teardrops" ) );
}
void buildFilterLists();
private:
PCB_EDIT_FRAME* m_parent;
BOARD* m_brd;
PCB_SELECTION m_selection;
UNIT_BINDER m_teardropHDPercent;
UNIT_BINDER m_teardropLenPercent;
UNIT_BINDER m_teardropMaxLen;
UNIT_BINDER m_teardropHeightPercent;
UNIT_BINDER m_teardropMaxHeight;
};
DIALOG_GLOBAL_EDIT_TEARDROPS::DIALOG_GLOBAL_EDIT_TEARDROPS( PCB_EDIT_FRAME* aParent ) :
DIALOG_GLOBAL_EDIT_TEARDROPS_BASE( aParent ),
m_teardropHDPercent( aParent, m_stHDRatio, m_tcHDRatio, m_stHDRatioUnits ),
m_teardropLenPercent( aParent, m_stLenPercentLabel, m_tcLenPercent, m_stLenPercentUnits ),
m_teardropMaxLen( aParent, m_stMaxLen, m_tcTdMaxLen, m_stMaxLenUnits ),
m_teardropHeightPercent( aParent, m_stHeightPercentLabel, m_tcHeightPercent, m_stHeightPercentUnits ),
m_teardropMaxHeight( aParent, m_stMaxHeight, m_tcMaxHeight, m_stMaxHeightUnits )
{
m_parent = aParent;
m_brd = m_parent->GetBoard();
m_bitmapTeardrop->SetBitmap( KiBitmap( BITMAPS::teardrop_sizes ) );
m_teardropHDPercent.SetUnits( EDA_UNITS::PERCENT );
m_teardropLenPercent.SetUnits( EDA_UNITS::PERCENT );
m_teardropHeightPercent.SetUnits( EDA_UNITS::PERCENT );
m_minTrackWidthHint->SetFont( KIUI::GetInfoFont( this ).Italic() );
buildFilterLists();
SetupStandardButtons();
m_netFilter->Connect( NET_SELECTED,
wxCommandEventHandler( DIALOG_GLOBAL_EDIT_TEARDROPS::OnNetFilterSelect ),
nullptr, this );
finishDialogSettings();
}
DIALOG_GLOBAL_EDIT_TEARDROPS::~DIALOG_GLOBAL_EDIT_TEARDROPS()
{
g_vias = m_vias->GetValue();
g_pthPads = m_pthPads->GetValue();
g_smdPads = m_smdPads->GetValue();
g_trackToTrack = m_trackToTrack->GetValue();
g_filterByNetclass = m_netclassFilterOpt->GetValue();
g_netclassFilter = m_netclassFilter->GetStringSelection();
g_filterByNet = m_netFilterOpt->GetValue();
g_netFilter = m_netFilter->GetSelectedNetname();
g_filterByLayer = m_layerFilterOpt->GetValue();
g_layerFilter = m_layerFilter->GetLayerSelection();
g_filterRoundPads = m_roundPadsFilter->GetValue();
g_filterSelected = m_selectedItemsFilter->GetValue();
if( m_removeTeardrops->GetValue() )
g_action = 0;
else if( m_specifiedValues->GetValue() )
g_action = 2;
else
g_action = 1;
m_netFilter->Disconnect( NET_SELECTED,
wxCommandEventHandler( DIALOG_GLOBAL_EDIT_TEARDROPS::OnNetFilterSelect ),
nullptr, this );
}
void DIALOG_GLOBAL_EDIT_TEARDROPS::buildFilterLists()
{
// Populate the net filter list with net names
m_netFilter->SetBoard( m_brd );
m_netFilter->SetNetInfo( &m_brd->GetNetInfo() );
if( !m_brd->GetHighLightNetCodes().empty() )
m_netFilter->SetSelectedNetcode( *m_brd->GetHighLightNetCodes().begin() );
// Populate the netclass filter list with netclass names
wxArrayString netclassNames;
std::shared_ptr<NET_SETTINGS>& settings = m_brd->GetDesignSettings().m_NetSettings;
netclassNames.push_back( settings->m_DefaultNetClass->GetName() );
for( const auto& [ name, netclass ] : settings->m_NetClasses )
netclassNames.push_back( name );
m_netclassFilter->Set( netclassNames );
m_netclassFilter->SetStringSelection( m_brd->GetDesignSettings().GetCurrentNetClassName() );
// Populate the layer filter list
m_layerFilter->SetBoardFrame( m_parent );
m_layerFilter->SetLayersHotkeys( false );
m_layerFilter->SetNotAllowedLayerSet( LSET::AllNonCuMask() );
m_layerFilter->Resync();
m_layerFilter->SetLayerSelection( m_parent->GetActiveLayer() );
}
bool DIALOG_GLOBAL_EDIT_TEARDROPS::TransferDataToWindow()
{
PCB_SELECTION_TOOL* selTool = m_parent->GetToolManager()->GetTool<PCB_SELECTION_TOOL>();
m_selection = selTool->GetSelection();
BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( m_selection.Front() );
m_vias->SetValue( g_vias );
m_pthPads->SetValue( g_pthPads );
m_smdPads->SetValue( g_smdPads );
m_trackToTrack->SetValue( g_trackToTrack );
if( g_filterByNetclass && m_netclassFilter->SetStringSelection( g_netclassFilter ) )
m_netclassFilterOpt->SetValue( true );
else if( item )
m_netclassFilter->SetStringSelection( item->GetNet()->GetNetClass()->GetName() );
if( g_filterByNet && m_brd->FindNet( g_netFilter ) != nullptr )
{
m_netFilter->SetSelectedNet( g_netFilter );
m_netFilterOpt->SetValue( true );
}
else if( item )
{
m_netFilter->SetSelectedNetcode( item->GetNetCode() );
}
if( g_filterByLayer && m_layerFilter->SetLayerSelection( g_layerFilter ) != wxNOT_FOUND )
{
m_layerFilterOpt->SetValue( true );
}
else if( item )
{
if( item->Type() == PCB_ZONE_T ) // a zone can be on more than one layer
m_layerFilter->SetLayerSelection( static_cast<ZONE*>(item)->GetFirstLayer() );
else
m_layerFilter->SetLayerSelection( item->GetLayer() );
}
m_roundPadsFilter->SetValue( g_filterRoundPads );
m_selectedItemsFilter->SetValue( g_filterSelected );
if( g_action == 0 )
m_removeTeardrops->SetValue( true );
else if( g_action == 1 )
m_addTeardrops->SetValue( true );
else
m_specifiedValues->SetValue( true );
m_cbPreferZoneConnection->Set3StateValue( wxCHK_UNDETERMINED );
m_cbTeardropsUseNextTrack->Set3StateValue( wxCHK_UNDETERMINED );
m_teardropHDPercent.SetValue( INDETERMINATE_ACTION );
m_teardropLenPercent.SetValue( INDETERMINATE_ACTION );
m_teardropMaxLen.SetValue( INDETERMINATE_ACTION );
m_teardropHeightPercent.SetValue( INDETERMINATE_ACTION );
m_teardropMaxHeight.SetValue( INDETERMINATE_ACTION );
m_curvedEdges->Set3StateValue( wxCHK_UNDETERMINED );
return true;
}
void DIALOG_GLOBAL_EDIT_TEARDROPS::setSpecifiedParams( TEARDROP_PARAMETERS* targetParams )
{
if( m_cbPreferZoneConnection->Get3StateValue() != wxCHK_UNDETERMINED )
targetParams->m_TdOnPadsInZones = !m_cbPreferZoneConnection->GetValue();
if( m_cbTeardropsUseNextTrack->Get3StateValue() != wxCHK_UNDETERMINED )
targetParams->m_AllowUseTwoTracks = m_cbTeardropsUseNextTrack->GetValue();
if( !m_teardropHDPercent.IsIndeterminate() )
targetParams->m_WidthtoSizeFilterRatio = m_teardropHDPercent.GetDoubleValue() / 100.0;
if( !m_teardropLenPercent.IsIndeterminate() )
targetParams->m_BestLengthRatio = m_teardropLenPercent.GetDoubleValue() / 100.0;
if( !m_teardropMaxLen.IsIndeterminate() )
targetParams->m_TdMaxLen = m_teardropMaxLen.GetIntValue();
if( !m_teardropHeightPercent.IsIndeterminate() )
targetParams->m_BestWidthRatio = m_teardropHeightPercent.GetDoubleValue() / 100.0;
if( !m_teardropMaxHeight.IsIndeterminate() )
targetParams->m_TdMaxWidth = m_teardropMaxHeight.GetIntValue();
if( m_curvedEdges->Get3StateValue() != wxCHK_UNDETERMINED )
{
if( m_curvedEdges->GetValue() )
targetParams->m_CurveSegCount = m_curvePointsCtrl->GetValue();
else
targetParams->m_CurveSegCount = 0;
}
}
void DIALOG_GLOBAL_EDIT_TEARDROPS::processItem( BOARD_COMMIT* aCommit, BOARD_CONNECTED_ITEM* aItem )
{
BOARD_DESIGN_SETTINGS& brdSettings = m_brd->GetDesignSettings();
TEARDROP_PARAMETERS* targetParams = nullptr;
if( aItem->Type() == PCB_PAD_T )
targetParams = &static_cast<PAD*>( aItem )->GetTeardropParams();
else if( aItem->Type() == PCB_VIA_T )
targetParams = &static_cast<PCB_VIA*>( aItem )->GetTeardropParams();
else
return;
aCommit->Stage( aItem, CHT_MODIFY );
if( m_removeTeardrops->GetValue() )
{
targetParams->m_Enabled = false;
}
else if( m_addTeardrops->GetValue() )
{
if( TEARDROP_MANAGER::IsRound( aItem ) )
*targetParams = *brdSettings.GetTeadropParamsList()->GetParameters( TARGET_ROUND );
else
*targetParams = *brdSettings.GetTeadropParamsList()->GetParameters( TARGET_RECT );
targetParams->m_Enabled = true;
}
else if( m_specifiedValues->GetValue() )
{
setSpecifiedParams( targetParams );
if( !m_existingFilter->GetValue() )
targetParams->m_Enabled = true;
}
}
void DIALOG_GLOBAL_EDIT_TEARDROPS::visitItem( BOARD_COMMIT* aCommit, BOARD_CONNECTED_ITEM* aItem )
{
if( m_selectedItemsFilter->GetValue() )
{
if( !aItem->IsSelected() )
{
PCB_GROUP* group = aItem->GetParentGroup();
while( group && !group->IsSelected() )
group = group->GetParentGroup();
if( !group )
return;
}
}
if( m_netFilterOpt->GetValue() && m_netFilter->GetSelectedNetcode() >= 0 )
{
if( aItem->GetNetCode() != m_netFilter->GetSelectedNetcode() )
return;
}
if( m_netclassFilterOpt->GetValue() && !m_netclassFilter->GetStringSelection().IsEmpty() )
{
if( aItem->GetEffectiveNetClass()->GetName() != m_netclassFilter->GetStringSelection() )
return;
}
if( m_layerFilterOpt->GetValue() && m_layerFilter->GetLayerSelection() != UNDEFINED_LAYER )
{
if( aItem->GetLayer() != m_layerFilter->GetLayerSelection() )
return;
}
if( m_roundPadsFilter->GetValue() )
{
if( !TEARDROP_MANAGER::IsRound( aItem ) )
return;
}
if( m_existingFilter->GetValue() )
{
if( aItem->Type() == PCB_PAD_T )
{
if( !static_cast<PAD*>( aItem )->GetTeardropParams().m_Enabled )
return;
}
else if( aItem->Type() == PCB_VIA_T )
{
if( !static_cast<PCB_VIA*>( aItem )->GetTeardropParams().m_Enabled )
return;
}
}
processItem( aCommit, aItem );
}
bool DIALOG_GLOBAL_EDIT_TEARDROPS::TransferDataFromWindow()
{
m_brd->SetLegacyTeardrops( false );
BOARD_COMMIT commit( m_parent );
wxBusyCursor dummy;
if( m_vias->GetValue() )
{
for( PCB_TRACK* track : m_brd->Tracks() )
{
if ( track->Type() == PCB_VIA_T )
visitItem( &commit, track );
}
}
for( FOOTPRINT* footprint : m_brd->Footprints() )
{
for( PAD* pad : footprint->Pads() )
{
if( m_pthPads->GetValue() && pad->GetAttribute() == PAD_ATTRIB::PTH )
{
visitItem( &commit, pad );
}
else if( m_smdPads->GetValue() && ( pad->GetAttribute() == PAD_ATTRIB::SMD
|| pad->GetAttribute() == PAD_ATTRIB::CONN ) )
{
visitItem( &commit, pad );
}
}
}
if( m_trackToTrack->GetValue() )
{
TEARDROP_PARAMETERS_LIST* paramsList = m_brd->GetDesignSettings().GetTeadropParamsList();
TEARDROP_PARAMETERS* targetParams = paramsList->GetParameters( TARGET_TRACK );
TEARDROP_MANAGER teardropManager( m_brd, m_parent->GetToolManager() );
teardropManager.DeleteTrackToTrackTeardrops( commit );
if( m_removeTeardrops->GetValue() )
{
targetParams->m_Enabled = false; // JEY TODO: how does this get undone/redone?
}
else if( m_addTeardrops->GetValue() )
{
targetParams->m_Enabled = true; // JEY TODO: how does this get undone/redone?
teardropManager.AddTeardropsOnTracks( commit, nullptr, true );
}
}
// If there are no filters then a force-full-update is equivalent, and will be faster.
if( !m_netFilterOpt->GetValue()
&& !m_netclassFilterOpt->GetValue()
&& !m_layerFilterOpt->GetValue()
&& !m_roundPadsFilter->GetValue()
&& !m_existingFilter->GetValue()
&& !m_selectedItemsFilter->GetValue() )
{
commit.Push( _( "Edit Teardrops" ), SKIP_TEARDROPS );
TEARDROP_MANAGER teardropMgr( m_brd, m_parent->GetToolManager() );
teardropMgr.UpdateTeardrops( commit, nullptr, nullptr, true /* forceFullUpdate */ );
commit.Push( _( "Edit Teardrops" ), SKIP_TEARDROPS | APPEND_UNDO );
}
else
{
commit.Push( _( "Edit Teardrops" ) );
}
// Showing the unfilled, fully cross-hatched teardrops seems to be working fairly well, and
// accurate fills can then be manually generated by doing a zone fill.
//
// But here's the old code which allowed for either "draft" fills or an automatic full zone
// fill in case we decide the current situation isn't good enough:
#if 0
if( aFillAfter )
{
ZONE_FILLER filler( m_board, aCommit );
if( m_reporter )
filler.SetProgressReporter( m_reporter );
filler.Fill( m_board->Zones() );
if( aCommit )
aCommit->Push( _( "Edit Teardrops" ), APPEND_UNDO );
}
else
{
// Fill raw teardrop shapes. This is a rough calculation, just to show a filled
// shape on screen without the (potentially large) performance hit of a zone refill
int epsilon = pcbIUScale.mmToIU( 0.001 );
int allowed_error = pcbIUScale.mmToIU( 0.005 );
for( ZONE* zone: m_createdTdList )
{
int half_min_width = zone->GetMinThickness() / 2;
int numSegs = GetArcToSegmentCount( half_min_width, allowed_error, FULL_CIRCLE );
SHAPE_POLY_SET filledPolys = *zone->Outline();
filledPolys.Deflate( half_min_width - epsilon, numSegs );
// Re-inflate after pruning of areas that don't meet minimum-width criteria
if( half_min_width - epsilon > epsilon )
filledPolys.Inflate( half_min_width - epsilon, numSegs );
zone->SetFilledPolysList( zone->GetFirstLayer(), filledPolys );
}
}
#endif
return true;
}
int GLOBAL_EDIT_TOOL::EditTeardrops( const TOOL_EVENT& aEvent )
{
PCB_EDIT_FRAME* editFrame = getEditFrame<PCB_EDIT_FRAME>();
DIALOG_GLOBAL_EDIT_TEARDROPS dlg( editFrame );
dlg.ShowQuasiModal(); // QuasiModal required for NET_SELECTOR
return 0;
}