/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2014-2017 CERN * Copyright (C) 2014-2023 KiCad Developers, see AUTHORS.txt for contributors. * @author Maciej Suminski * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcb_actions.h" #include "zone_filler_tool.h" #include "zone_filler.h" #include "teardrop/teardrop.h" #include ZONE_FILLER_TOOL::ZONE_FILLER_TOOL() : PCB_TOOL_BASE( "pcbnew.ZoneFiller" ), m_fillInProgress( false ) { } ZONE_FILLER_TOOL::~ZONE_FILLER_TOOL() { } void ZONE_FILLER_TOOL::Reset( RESET_REASON aReason ) { } void ZONE_FILLER_TOOL::CheckAllZones( wxWindow* aCaller, PROGRESS_REPORTER* aReporter ) { if( !getEditFrame()->m_ZoneFillsDirty || m_fillInProgress ) return; m_fillInProgress = true; std::vector toFill; for( ZONE* zone : board()->Zones() ) toFill.push_back( zone ); BOARD_COMMIT commit( this ); std::unique_ptr reporter; m_filler = std::make_unique( frame()->GetBoard(), &commit ); if( aReporter ) { m_filler->SetProgressReporter( aReporter ); } else { reporter = std::make_unique( aCaller, _( "Checking Zones" ), 4 ); m_filler->SetProgressReporter( reporter.get() ); } if( m_filler->Fill( toFill, true, aCaller ) ) { commit.Push( _( "Fill Zone(s)" ), SKIP_CONNECTIVITY | ZONE_FILL_OP ); getEditFrame()->m_ZoneFillsDirty = false; } else { commit.Revert(); } rebuildConnectivity(); refresh(); m_fillInProgress = false; m_filler.reset( nullptr ); } void ZONE_FILLER_TOOL::singleShotRefocus( wxIdleEvent& ) { canvas()->SetFocus(); canvas()->Unbind( wxEVT_IDLE, &ZONE_FILLER_TOOL::singleShotRefocus, this ); } void ZONE_FILLER_TOOL::FillAllZones( wxWindow* aCaller, PROGRESS_REPORTER* aReporter ) { if( m_fillInProgress ) return; m_fillInProgress = true; PCB_EDIT_FRAME* frame = getEditFrame(); BOARD_COMMIT commit( this ); std::unique_ptr reporter; TEARDROP_MANAGER teardropMgr( board(), m_toolMgr ); std::vector toFill; teardropMgr.UpdateTeardrops( commit, nullptr, nullptr, true /* forceFullUpdate */ ); board()->IncrementTimeStamp(); // Clear caches for( ZONE* zone : board()->Zones() ) toFill.push_back( zone ); m_filler = std::make_unique( board(), &commit ); if( !board()->GetDesignSettings().m_DRCEngine->RulesValid() ) { WX_INFOBAR* infobar = frame->GetInfoBar(); wxHyperlinkCtrl* button = new wxHyperlinkCtrl( infobar, wxID_ANY, _( "Show DRC rules" ), wxEmptyString ); button->Bind( wxEVT_COMMAND_HYPERLINK, std::function( [frame]( wxHyperlinkEvent& aEvent ) { frame->ShowBoardSetupDialog( _( "Rules" ) ); } ) ); infobar->RemoveAllButtons(); infobar->AddButton( button ); infobar->ShowMessageFor( _( "Zone fills may be inaccurate. DRC rules contain errors." ), 10000, wxICON_WARNING ); } if( aReporter ) { m_filler->SetProgressReporter( aReporter ); } else { reporter = std::make_unique( aCaller, _( "Fill All Zones" ), 5 ); m_filler->SetProgressReporter( reporter.get() ); } if( m_filler->Fill( toFill ) ) { m_filler->GetProgressReporter()->AdvancePhase(); commit.Push( _( "Fill Zone(s)" ), SKIP_CONNECTIVITY | ZONE_FILL_OP ); frame->m_ZoneFillsDirty = false; } else { commit.Revert(); } rebuildConnectivity(); refresh(); if( m_filler->IsDebug() ) frame->UpdateUserInterface(); m_fillInProgress = false; m_filler.reset( nullptr ); // wxWidgets has keyboard focus issues after the progress reporter. Re-setting the focus // here doesn't work, so we delay it to an idle event. canvas()->Bind( wxEVT_IDLE, &ZONE_FILLER_TOOL::singleShotRefocus, this ); } int ZONE_FILLER_TOOL::ZoneFillDirty( const TOOL_EVENT& aEvent ) { PCB_EDIT_FRAME* frame = getEditFrame(); std::vector toFill; for( ZONE* zone : board()->Zones() ) { if( !zone->IsFilled() || m_dirtyZoneIDs.count( zone->m_Uuid ) ) toFill.push_back( zone ); } if( toFill.empty() ) return 0; if( m_fillInProgress ) return 0; int64_t startTime = GetRunningMicroSecs(); m_fillInProgress = true; m_dirtyZoneIDs.clear(); board()->IncrementTimeStamp(); // Clear caches BOARD_COMMIT commit( this ); std::unique_ptr reporter; int pts = 0; m_filler = std::make_unique( board(), &commit ); if( !board()->GetDesignSettings().m_DRCEngine->RulesValid() ) { WX_INFOBAR* infobar = frame->GetInfoBar(); wxHyperlinkCtrl* button = new wxHyperlinkCtrl( infobar, wxID_ANY, _( "Show DRC rules" ), wxEmptyString ); button->Bind( wxEVT_COMMAND_HYPERLINK, std::function( [frame]( wxHyperlinkEvent& aLocEvent ) { frame->ShowBoardSetupDialog( _( "Rules" ) ); } ) ); infobar->RemoveAllButtons(); infobar->AddButton( button ); infobar->ShowMessageFor( _( "Zone fills may be inaccurate. DRC rules contain errors." ), 10000, wxICON_WARNING ); } for( ZONE* zone : toFill ) { zone->GetLayerSet().RunOnLayers( [&]( PCB_LAYER_ID layer ) { pts += zone->GetFilledPolysList( layer )->FullPointCount(); } ); if( pts > 1000 ) { wxString title = wxString::Format( _( "Refill %d Zones" ), (int) toFill.size() ); reporter = std::make_unique( frame, title, 5 ); m_filler->SetProgressReporter( reporter.get() ); break; } } if( m_filler->Fill( toFill ) ) commit.Push( _( "Auto-fill Zone(s)" ), APPEND_UNDO | SKIP_CONNECTIVITY | ZONE_FILL_OP ); else commit.Revert(); rebuildConnectivity(); refresh(); if( GetRunningMicroSecs() - startTime > 3000000 ) // 3 seconds { WX_INFOBAR* infobar = frame->GetInfoBar(); wxHyperlinkCtrl* button = new wxHyperlinkCtrl( infobar, wxID_ANY, _( "Open Preferences" ), wxEmptyString ); button->Bind( wxEVT_COMMAND_HYPERLINK, std::function( [this]( wxHyperlinkEvent& ) { getEditFrame()->ShowPreferences( _( "Editing Options" ), _( "PCB Editor" ) ); } ) ); infobar->RemoveAllButtons(); infobar->AddButton( button ); infobar->ShowMessageFor( _( "Automatic refill of zones can be turned off in Preferences " "if it becomes too slow." ), 10000, wxICON_INFORMATION, WX_INFOBAR::MESSAGE_TYPE::GENERIC ); } if( m_filler->IsDebug() ) frame->UpdateUserInterface(); m_fillInProgress = false; m_filler.reset( nullptr ); // wxWidgets has keyboard focus issues after the progress reporter. Re-setting the focus // here doesn't work, so we delay it to an idle event. canvas()->Bind( wxEVT_IDLE, &ZONE_FILLER_TOOL::singleShotRefocus, this ); return 0; } int ZONE_FILLER_TOOL::ZoneFill( const TOOL_EVENT& aEvent ) { if( m_fillInProgress ) { wxBell(); return -1; } std::vector toFill; if( ZONE* passedZone = aEvent.Parameter() ) { toFill.push_back( passedZone ); } else { const PCB_SELECTION& sel = m_toolMgr->GetTool()->RequestSelection( []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool ) { } ); for( EDA_ITEM* item : sel ) { if( ZONE* zone = dynamic_cast( item ) ) toFill.push_back( zone ); } } // Bail out of the filler if there is nothing to fill if( toFill.empty() ) { wxBell(); return -1; } m_fillInProgress = true; BOARD_COMMIT commit( this ); std::unique_ptr reporter; m_filler = std::make_unique( board(), &commit ); reporter = std::make_unique( frame(), _( "Fill Zone" ), 5 ); m_filler->SetProgressReporter( reporter.get() ); if( m_filler->Fill( toFill ) ) { reporter->AdvancePhase(); commit.Push( _( "Fill Zone(s)" ), SKIP_CONNECTIVITY | ZONE_FILL_OP ); } else { commit.Revert(); } rebuildConnectivity(); refresh(); m_fillInProgress = false; m_filler.reset( nullptr ); return 0; } int ZONE_FILLER_TOOL::ZoneFillAll( const TOOL_EVENT& aEvent ) { FillAllZones( frame() ); return 0; } int ZONE_FILLER_TOOL::ZoneUnfill( const TOOL_EVENT& aEvent ) { const PCB_SELECTION& sel = m_toolMgr->GetTool()->RequestSelection( []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool ) { } ); std::vector toUnfill; for( EDA_ITEM* item : sel ) { if( ZONE* zone = dynamic_cast( item ) ) toUnfill.push_back( zone ); } // Bail out if there are no zones if( toUnfill.empty() ) { wxBell(); return -1; } BOARD_COMMIT commit( this ); for( ZONE* zone : toUnfill ) { commit.Modify( zone ); zone->UnFill(); } commit.Push( _( "Unfill Zone" ), ZONE_FILL_OP ); refresh(); return 0; } int ZONE_FILLER_TOOL::ZoneUnfillAll( const TOOL_EVENT& aEvent ) { BOARD_COMMIT commit( this ); for( ZONE* zone : board()->Zones() ) { commit.Modify( zone ); zone->UnFill(); } commit.Push( _( "Unfill All Zones" ), ZONE_FILL_OP ); refresh(); return 0; } PROGRESS_REPORTER* ZONE_FILLER_TOOL::GetProgressReporter() { if( m_fillInProgress && m_filler ) return m_filler->GetProgressReporter(); else return nullptr; } void ZONE_FILLER_TOOL::rebuildConnectivity() { board()->BuildConnectivity(); m_toolMgr->PostEvent( EVENTS::ConnectivityChangedEvent ); canvas()->RedrawRatsnest(); } void ZONE_FILLER_TOOL::refresh() { // Note: KIGFX::REPAINT isn't enough for things that go from invisible to visible as // they won't be found in the view layer's itemset for re-painting. canvas()->GetView()->UpdateAllItemsConditionally( KIGFX::ALL, [&]( KIGFX::VIEW_ITEM* aItem ) -> bool { if( PCB_VIA* via = dynamic_cast( aItem ) ) { return via->GetRemoveUnconnected(); } else if( PAD* pad = dynamic_cast( aItem ) ) { return pad->GetRemoveUnconnected(); } return false; } ); canvas()->Refresh(); } bool ZONE_FILLER_TOOL::IsZoneFillAction( const TOOL_EVENT* aEvent ) { return aEvent->IsAction( &PCB_ACTIONS::zoneFill ) || aEvent->IsAction( &PCB_ACTIONS::zoneFillAll ) || aEvent->IsAction( &PCB_ACTIONS::zoneUnfill ) || aEvent->IsAction( &PCB_ACTIONS::zoneUnfillAll ); // Don't include zoneFillDirty; that's a system action not a user action } void ZONE_FILLER_TOOL::setTransitions() { // Zone actions Go( &ZONE_FILLER_TOOL::ZoneFill, PCB_ACTIONS::zoneFill.MakeEvent() ); Go( &ZONE_FILLER_TOOL::ZoneFillAll, PCB_ACTIONS::zoneFillAll.MakeEvent() ); Go( &ZONE_FILLER_TOOL::ZoneFillDirty, PCB_ACTIONS::zoneFillDirty.MakeEvent() ); Go( &ZONE_FILLER_TOOL::ZoneUnfill, PCB_ACTIONS::zoneUnfill.MakeEvent() ); Go( &ZONE_FILLER_TOOL::ZoneUnfillAll, PCB_ACTIONS::zoneUnfillAll.MakeEvent() ); }