1255 lines
38 KiB
C++
1255 lines
38 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2018 Jean-Pierre Charras, jean-pierre.charras@ujf-grenoble.fr
|
|
* Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
|
|
* Copyright (C) 2011 Wayne Stambaugh <stambaughw@gmail.com>
|
|
* Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
|
* Copyright (C) 2022 CERN
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
/// @todo The Boost entropy exception does not exist prior to 1.67. Once the minimum Boost
|
|
/// version is raise to 1.67 or greater, this version check can be removed.
|
|
#include <boost/version.hpp>
|
|
|
|
#if BOOST_VERSION >= 106700
|
|
#include <boost/uuid/entropy_error.hpp>
|
|
#endif
|
|
|
|
#include <3d_viewer/eda_3d_viewer_frame.h> // To include VIEWER3D_FRAMENAME
|
|
#include <advanced_config.h>
|
|
#include <base_units.h>
|
|
#include <board.h>
|
|
#include <cleanup_item.h>
|
|
#include <collectors.h>
|
|
#include <confirm.h>
|
|
#include <footprint.h>
|
|
#include <footprint_editor_settings.h>
|
|
#include <fp_lib_table.h>
|
|
#include <kiface_base.h>
|
|
#include <pcb_group.h>
|
|
#include <pcb_painter.h>
|
|
#include <pcbnew_id.h>
|
|
#include <pcbnew_settings.h>
|
|
#include <pcb_base_frame.h>
|
|
#include <pcb_draw_panel_gal.h>
|
|
#include <pgm_base.h>
|
|
#include <wildcards_and_files_ext.h>
|
|
#include <zoom_defines.h>
|
|
|
|
#include <math/vector2d.h>
|
|
#include <math/vector2wx.h>
|
|
#include <widgets/msgpanel.h>
|
|
|
|
#include <settings/settings_manager.h>
|
|
#include <settings/cvpcb_settings.h>
|
|
#include <tool/tool_manager.h>
|
|
#include <tool/tool_dispatcher.h>
|
|
#include <tools/pcb_actions.h>
|
|
#include <tool/grid_menu.h>
|
|
#include <ratsnest/ratsnest_view_item.h>
|
|
|
|
#include <navlib/nl_pcbnew_plugin.h>
|
|
|
|
using KIGFX::RENDER_SETTINGS;
|
|
using KIGFX::PCB_RENDER_SETTINGS;
|
|
|
|
wxDEFINE_EVENT( EDA_EVT_BOARD_CHANGED, wxCommandEvent );
|
|
|
|
PCB_BASE_FRAME::PCB_BASE_FRAME( KIWAY* aKiway, wxWindow* aParent, FRAME_T aFrameType,
|
|
const wxString& aTitle, const wxPoint& aPos, const wxSize& aSize,
|
|
long aStyle, const wxString& aFrameName ) :
|
|
EDA_DRAW_FRAME( aKiway, aParent, aFrameType, aTitle, aPos, aSize, aStyle, aFrameName,
|
|
pcbIUScale ),
|
|
m_pcb( nullptr ),
|
|
m_originTransforms( *this ),
|
|
m_spaceMouse( nullptr )
|
|
{
|
|
m_watcherDebounceTimer.Bind( wxEVT_TIMER, &PCB_BASE_FRAME::OnFpChangeDebounceTimer, this );
|
|
}
|
|
|
|
|
|
PCB_BASE_FRAME::~PCB_BASE_FRAME()
|
|
{
|
|
delete m_spaceMouse;
|
|
m_spaceMouse = nullptr;
|
|
|
|
// Ensure m_canvasType is up to date, to save it in config
|
|
m_canvasType = GetCanvas()->GetBackend();
|
|
|
|
delete m_pcb;
|
|
m_pcb = nullptr;
|
|
}
|
|
|
|
|
|
bool PCB_BASE_FRAME::canCloseWindow( wxCloseEvent& aEvent )
|
|
{
|
|
// Close modeless dialogs. They're trouble when they get destroyed after the frame and/or
|
|
// board.
|
|
wxWindow* viewer3D = Get3DViewerFrame();
|
|
|
|
if( viewer3D )
|
|
viewer3D->Close( true );
|
|
|
|
// Similarly, wxConvBrokenFileNames uses some statically allocated variables that make it
|
|
// crash when run later from a d'tor.
|
|
Prj().Cleanup3DCache();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::handleActivateEvent( wxActivateEvent& aEvent )
|
|
{
|
|
EDA_DRAW_FRAME::handleActivateEvent( aEvent );
|
|
|
|
if( m_spaceMouse != nullptr && ADVANCED_CFG::GetCfg().m_Use3DConnexionDriver )
|
|
m_spaceMouse->SetFocus( aEvent.GetActive() );
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::handleIconizeEvent( wxIconizeEvent& aEvent )
|
|
{
|
|
EDA_DRAW_FRAME::handleIconizeEvent( aEvent );
|
|
|
|
if( m_spaceMouse != nullptr && aEvent.IsIconized()
|
|
&& ADVANCED_CFG::GetCfg().m_Use3DConnexionDriver )
|
|
m_spaceMouse->SetFocus( false );
|
|
}
|
|
|
|
|
|
EDA_3D_VIEWER_FRAME* PCB_BASE_FRAME::Get3DViewerFrame()
|
|
{
|
|
wxWindow* frame = FindWindowByName( QUALIFIED_VIEWER3D_FRAMENAME( this ) );
|
|
return dynamic_cast<EDA_3D_VIEWER_FRAME*>( frame );
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::Update3DView( bool aMarkDirty, bool aRefresh, const wxString* aTitle )
|
|
{
|
|
EDA_3D_VIEWER_FRAME* draw3DFrame = Get3DViewerFrame();
|
|
|
|
if( draw3DFrame )
|
|
{
|
|
if( aTitle )
|
|
draw3DFrame->SetTitle( *aTitle );
|
|
|
|
if( aMarkDirty )
|
|
draw3DFrame->ReloadRequest();
|
|
|
|
if( aRefresh )
|
|
draw3DFrame->Redraw();
|
|
}
|
|
}
|
|
|
|
|
|
FP_LIB_TABLE* PROJECT::PcbFootprintLibs()
|
|
{
|
|
// This is a lazy loading function, it loads the project specific table when
|
|
// that table is asked for, not before.
|
|
|
|
FP_LIB_TABLE* tbl = (FP_LIB_TABLE*) GetElem( ELEM_FPTBL );
|
|
|
|
// its gotta be NULL or a FP_LIB_TABLE, or a bug.
|
|
wxASSERT( !tbl || tbl->Type() == FP_LIB_TABLE_T );
|
|
|
|
if( !tbl )
|
|
{
|
|
// Stack the project specific FP_LIB_TABLE overlay on top of the global table.
|
|
// ~FP_LIB_TABLE() will not touch the fallback table, so multiple projects may
|
|
// stack this way, all using the same global fallback table.
|
|
tbl = new FP_LIB_TABLE( &GFootprintTable );
|
|
|
|
SetElem( ELEM_FPTBL, tbl );
|
|
|
|
wxString projectFpLibTableFileName = FootprintLibTblName();
|
|
|
|
try
|
|
{
|
|
tbl->Load( projectFpLibTableFileName );
|
|
}
|
|
catch( const IO_ERROR& ioe )
|
|
{
|
|
DisplayErrorMessage( nullptr, _( "Error loading project footprint libraries." ),
|
|
ioe.What() );
|
|
}
|
|
catch( ... )
|
|
{
|
|
DisplayErrorMessage( nullptr, _( "Error loading project footprint library table." ) );
|
|
}
|
|
}
|
|
|
|
return tbl;
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::SetBoard( BOARD* aBoard, PROGRESS_REPORTER* aReporter )
|
|
{
|
|
if( m_pcb != aBoard )
|
|
{
|
|
delete m_pcb;
|
|
m_pcb = aBoard;
|
|
|
|
if( GetBoard() )
|
|
GetBoard()->SetUserUnits( GetUserUnits() );
|
|
|
|
if( GetBoard() && GetCanvas() )
|
|
{
|
|
RENDER_SETTINGS* rs = GetCanvas()->GetView()->GetPainter()->GetSettings();
|
|
|
|
if( rs )
|
|
{
|
|
rs->SetDashLengthRatio( GetBoard()->GetPlotOptions().GetDashedLineDashRatio() );
|
|
rs->SetGapLengthRatio( GetBoard()->GetPlotOptions().GetDashedLineGapRatio() );
|
|
}
|
|
}
|
|
|
|
wxCommandEvent e( EDA_EVT_BOARD_CHANGED );
|
|
ProcessEventLocally( e );
|
|
|
|
for( wxEvtHandler* listener : m_boardChangeListeners )
|
|
{
|
|
wxCHECK2( listener, continue );
|
|
|
|
// Use the windows variant when handling event messages in case there is any special
|
|
// event handler pre and/or post processing specific to windows.
|
|
wxWindow* win = dynamic_cast<wxWindow*>( listener );
|
|
|
|
if( win )
|
|
win->HandleWindowEvent( e );
|
|
else
|
|
listener->SafelyProcessEvent( e );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::AddBoardChangeListener( wxEvtHandler* aListener )
|
|
{
|
|
auto it = std::find( m_boardChangeListeners.begin(), m_boardChangeListeners.end(), aListener );
|
|
|
|
// Don't add duplicate listeners.
|
|
if( it == m_boardChangeListeners.end() )
|
|
m_boardChangeListeners.push_back( aListener );
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::RemoveBoardChangeListener( wxEvtHandler* aListener )
|
|
{
|
|
auto it = std::find( m_boardChangeListeners.begin(), m_boardChangeListeners.end(), aListener );
|
|
|
|
// Don't add duplicate listeners.
|
|
if( it != m_boardChangeListeners.end() )
|
|
m_boardChangeListeners.erase( it );
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::AddFootprintToBoard( FOOTPRINT* aFootprint )
|
|
{
|
|
if( aFootprint )
|
|
{
|
|
GetBoard()->Add( aFootprint, ADD_MODE::APPEND );
|
|
|
|
aFootprint->SetFlags( IS_NEW );
|
|
aFootprint->SetPosition( VECTOR2I( 0, 0 ) ); // cursor in GAL may not be initialized yet
|
|
|
|
// Put it on FRONT layer (note that it might be stored flipped if the lib is an archive
|
|
// built from a board)
|
|
if( aFootprint->IsFlipped() )
|
|
aFootprint->Flip( aFootprint->GetPosition(), GetPcbNewSettings()->m_FlipLeftRight );
|
|
|
|
// Place it in orientation 0 even if it is not saved with orientation 0 in lib (note that
|
|
// it might be stored in another orientation if the lib is an archive built from a board)
|
|
aFootprint->SetOrientation( ANGLE_0 );
|
|
|
|
GetBoard()->UpdateUserUnits( aFootprint, GetCanvas()->GetView() );
|
|
}
|
|
}
|
|
|
|
|
|
EDA_ITEM* PCB_BASE_FRAME::GetItem( const KIID& aId ) const
|
|
{
|
|
return GetBoard()->GetItem( aId );
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::FocusOnItem( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer )
|
|
{
|
|
std::vector<BOARD_ITEM*> items;
|
|
|
|
if( aItem )
|
|
items.push_back( aItem );
|
|
|
|
FocusOnItems( items, aLayer );
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::FocusOnItems( std::vector<BOARD_ITEM*> aItems, PCB_LAYER_ID aLayer )
|
|
{
|
|
static std::vector<KIID> lastBrightenedItemIDs;
|
|
|
|
BOARD_ITEM* lastItem = nullptr;
|
|
|
|
for( KIID lastBrightenedItemID : lastBrightenedItemIDs )
|
|
{
|
|
/// @todo The Boost entropy exception does not exist prior to 1.67. Once the minimum Boost
|
|
/// version is raise to 1.67 or greater, this version check can be removed.
|
|
#if BOOST_VERSION >= 106700
|
|
try
|
|
{
|
|
lastItem = GetBoard()->GetItem( lastBrightenedItemID );
|
|
}
|
|
catch( const boost::uuids::entropy_error& )
|
|
{
|
|
wxLogError( wxT( "A Boost UUID entropy exception was thrown in %s:%s." ),
|
|
__FILE__, __FUNCTION__ );
|
|
}
|
|
#else
|
|
lastItem = GetBoard()->GetItem( lastBrightenedItemID );
|
|
#endif
|
|
|
|
if( lastItem && lastItem != DELETED_BOARD_ITEM::GetInstance() )
|
|
{
|
|
lastItem->ClearBrightened();
|
|
|
|
if( lastItem->Type() == PCB_FOOTPRINT_T )
|
|
{
|
|
static_cast<FOOTPRINT*>( lastItem )->RunOnChildren(
|
|
[&]( BOARD_ITEM* child )
|
|
{
|
|
child->ClearBrightened();
|
|
} );
|
|
}
|
|
else if( lastItem->Type() == PCB_GROUP_T )
|
|
{
|
|
static_cast<PCB_GROUP*>( lastItem )->RunOnChildren(
|
|
[&]( BOARD_ITEM* child )
|
|
{
|
|
child->ClearBrightened();
|
|
} );
|
|
}
|
|
|
|
GetCanvas()->GetView()->Update( lastItem );
|
|
lastBrightenedItemID = niluuid;
|
|
GetCanvas()->Refresh();
|
|
}
|
|
}
|
|
|
|
lastBrightenedItemIDs.clear();
|
|
|
|
if( aItems.empty() )
|
|
return;
|
|
|
|
VECTOR2I focusPt;
|
|
KIGFX::VIEW* view = GetCanvas()->GetView();
|
|
SHAPE_POLY_SET viewportPoly( view->GetViewport() );
|
|
|
|
for( wxWindow* dialog : findDialogs() )
|
|
{
|
|
wxPoint dialogPos = GetCanvas()->ScreenToClient( dialog->GetScreenPosition() );
|
|
SHAPE_POLY_SET dialogPoly( BOX2D( view->ToWorld( ToVECTOR2D( dialogPos ), true ),
|
|
view->ToWorld( ToVECTOR2D( dialog->GetSize() ), false ) ) );
|
|
|
|
try
|
|
{
|
|
viewportPoly.BooleanSubtract( dialogPoly, SHAPE_POLY_SET::PM_FAST );
|
|
}
|
|
catch( const std::exception& exc )
|
|
{
|
|
// This may be overkill and could be an assertion but we are more likely to
|
|
// find any clipper errors this way.
|
|
wxLogError( wxT( "Clipper library exception '%s' occurred." ), exc.what() );
|
|
}
|
|
}
|
|
|
|
SHAPE_POLY_SET itemPoly, clippedPoly;
|
|
|
|
for( BOARD_ITEM* item : aItems )
|
|
{
|
|
if( item && item != DELETED_BOARD_ITEM::GetInstance() )
|
|
{
|
|
item->SetBrightened();
|
|
|
|
if( item->Type() == PCB_FOOTPRINT_T )
|
|
{
|
|
static_cast<FOOTPRINT*>( item )->RunOnChildren(
|
|
[&]( BOARD_ITEM* child )
|
|
{
|
|
child->SetBrightened();
|
|
});
|
|
}
|
|
else if( item->Type() == PCB_GROUP_T )
|
|
{
|
|
static_cast<PCB_GROUP*>( item )->RunOnChildren(
|
|
[&]( BOARD_ITEM* child )
|
|
{
|
|
child->SetBrightened();
|
|
});
|
|
}
|
|
|
|
GetCanvas()->GetView()->Update( item );
|
|
lastBrightenedItemIDs.push_back( item->m_Uuid );
|
|
|
|
// Focus on the object's location. Prefer a visible part of the object to its anchor
|
|
// in order to keep from scrolling around.
|
|
|
|
focusPt = item->GetPosition();
|
|
|
|
if( aLayer == UNDEFINED_LAYER )
|
|
aLayer = item->GetLayerSet().Seq()[0];
|
|
|
|
switch( item->Type() )
|
|
{
|
|
case PCB_FOOTPRINT_T:
|
|
try
|
|
{
|
|
itemPoly = static_cast<FOOTPRINT*>( item )->GetBoundingHull();
|
|
}
|
|
catch( const std::exception& exc )
|
|
{
|
|
// This may be overkill and could be an assertion but we are more likely to
|
|
// find any clipper errors this way.
|
|
wxLogError( wxT( "Clipper library exception '%s' occurred." ), exc.what() );
|
|
}
|
|
|
|
break;
|
|
|
|
case PCB_PAD_T:
|
|
case PCB_MARKER_T:
|
|
case PCB_VIA_T:
|
|
FocusOnLocation( item->GetFocusPosition() );
|
|
GetCanvas()->Refresh();
|
|
return;
|
|
|
|
case PCB_SHAPE_T:
|
|
case PCB_TEXT_T:
|
|
case PCB_TEXTBOX_T:
|
|
case PCB_TRACE_T:
|
|
case PCB_ARC_T:
|
|
case PCB_DIM_ALIGNED_T:
|
|
case PCB_DIM_LEADER_T:
|
|
case PCB_DIM_CENTER_T:
|
|
case PCB_DIM_RADIAL_T:
|
|
case PCB_DIM_ORTHOGONAL_T:
|
|
item->TransformShapeToPolygon( itemPoly, aLayer, 0, pcbIUScale.mmToIU( 0.1 ),
|
|
ERROR_INSIDE );
|
|
break;
|
|
|
|
case PCB_ZONE_T:
|
|
{
|
|
ZONE* zone = static_cast<ZONE*>( item );
|
|
#if 0
|
|
// Using the filled area shapes to find a Focus point can give good results, but
|
|
// unfortunately the calculations are highly time consuming, even for not very
|
|
// large areas (can be easily a few minutes for large areas).
|
|
// so we used only the zone outline that usually do not have too many vertices.
|
|
zone->TransformShapeToPolygon( itemPoly, aLayer, 0, pcbIUScale.mmToIU( 0.1 ),
|
|
ERROR_INSIDE );
|
|
|
|
if( itemPoly.IsEmpty() )
|
|
itemPoly = *zone->Outline();
|
|
#else
|
|
// much faster calculation time when using only the zone outlines
|
|
itemPoly = *zone->Outline();
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
BOX2I item_bbox = item->GetBoundingBox();
|
|
itemPoly.NewOutline();
|
|
itemPoly.Append( item_bbox.GetOrigin() );
|
|
itemPoly.Append( item_bbox.GetOrigin() + VECTOR2I( item_bbox.GetWidth(), 0 ) );
|
|
itemPoly.Append( item_bbox.GetOrigin() + VECTOR2I( 0, item_bbox.GetHeight() ) );
|
|
itemPoly.Append( item_bbox.GetOrigin() + VECTOR2I( item_bbox.GetWidth(),
|
|
item_bbox.GetHeight() ) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
clippedPoly.BooleanIntersection( itemPoly, viewportPoly, SHAPE_POLY_SET::PM_FAST );
|
|
}
|
|
catch( const std::exception& exc )
|
|
{
|
|
// This may be overkill and could be an assertion but we are more likely to
|
|
// find any clipper errors this way.
|
|
wxLogError( wxT( "Clipper library exception '%s' occurred." ), exc.what() );
|
|
}
|
|
|
|
if( !clippedPoly.IsEmpty() )
|
|
itemPoly = clippedPoly;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Perform a step-wise deflate to find the visual-center-of-mass
|
|
*/
|
|
|
|
BOX2I bbox = itemPoly.BBox();
|
|
int step = std::min( bbox.GetWidth(), bbox.GetHeight() ) / 10;
|
|
|
|
while( !itemPoly.IsEmpty() )
|
|
{
|
|
focusPt = itemPoly.BBox().Centre();
|
|
|
|
try
|
|
{
|
|
itemPoly.Deflate( step, SHAPE_POLY_SET::ALLOW_ACUTE_CORNERS, ARC_LOW_DEF );
|
|
}
|
|
catch( const std::exception& exc )
|
|
{
|
|
// This may be overkill and could be an assertion but we are more likely to
|
|
// find any clipper errors this way.
|
|
wxLogError( wxT( "Clipper library exception '%s' occurred." ), exc.what() );
|
|
}
|
|
}
|
|
|
|
FocusOnLocation( focusPt );
|
|
|
|
GetCanvas()->Refresh();
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::HideSolderMask()
|
|
{
|
|
KIGFX::PCB_VIEW* view = GetCanvas()->GetView();
|
|
|
|
if( view && GetBoard()->m_SolderMask && view->HasItem( GetBoard()->m_SolderMask ) )
|
|
view->Remove( GetBoard()->m_SolderMask );
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::ShowSolderMask()
|
|
{
|
|
KIGFX::PCB_VIEW* view = GetCanvas()->GetView();
|
|
|
|
if( view && GetBoard()->m_SolderMask )
|
|
{
|
|
if( view->HasItem( GetBoard()->m_SolderMask ) )
|
|
view->Remove( GetBoard()->m_SolderMask );
|
|
|
|
view->Add( GetBoard()->m_SolderMask );
|
|
}
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::SetPageSettings( const PAGE_INFO& aPageSettings )
|
|
{
|
|
m_pcb->SetPageSettings( aPageSettings );
|
|
|
|
if( GetScreen() )
|
|
GetScreen()->InitDataPoints( aPageSettings.GetSizeIU( pcbIUScale.IU_PER_MILS ) );
|
|
}
|
|
|
|
|
|
const PAGE_INFO& PCB_BASE_FRAME::GetPageSettings() const
|
|
{
|
|
return m_pcb->GetPageSettings();
|
|
}
|
|
|
|
|
|
const VECTOR2I PCB_BASE_FRAME::GetPageSizeIU() const
|
|
{
|
|
// this function is only needed because EDA_DRAW_FRAME is not compiled
|
|
// with either -DPCBNEW or -DEESCHEMA, so the virtual is used to route
|
|
// into an application specific source file.
|
|
return m_pcb->GetPageSettings().GetSizeIU( pcbIUScale.IU_PER_MILS );
|
|
}
|
|
|
|
|
|
const VECTOR2I& PCB_BASE_FRAME::GetGridOrigin() const
|
|
{
|
|
return m_pcb->GetDesignSettings().GetGridOrigin();
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::SetGridOrigin( const VECTOR2I& aPoint )
|
|
{
|
|
m_pcb->GetDesignSettings().SetGridOrigin( aPoint );
|
|
}
|
|
|
|
|
|
const VECTOR2I& PCB_BASE_FRAME::GetAuxOrigin() const
|
|
{
|
|
return m_pcb->GetDesignSettings().GetAuxOrigin();
|
|
}
|
|
|
|
|
|
const VECTOR2I PCB_BASE_FRAME::GetUserOrigin() const
|
|
{
|
|
VECTOR2I origin( 0, 0 );
|
|
|
|
switch( GetPcbNewSettings()->m_Display.m_DisplayOrigin )
|
|
{
|
|
case PCB_DISPLAY_ORIGIN::PCB_ORIGIN_PAGE: break;
|
|
case PCB_DISPLAY_ORIGIN::PCB_ORIGIN_AUX: origin = GetAuxOrigin(); break;
|
|
case PCB_DISPLAY_ORIGIN::PCB_ORIGIN_GRID: origin = GetGridOrigin(); break;
|
|
default: wxASSERT( false ); break;
|
|
}
|
|
|
|
return origin;
|
|
}
|
|
|
|
ORIGIN_TRANSFORMS& PCB_BASE_FRAME::GetOriginTransforms()
|
|
{
|
|
return m_originTransforms;
|
|
}
|
|
|
|
|
|
const TITLE_BLOCK& PCB_BASE_FRAME::GetTitleBlock() const
|
|
{
|
|
return m_pcb->GetTitleBlock();
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::SetTitleBlock( const TITLE_BLOCK& aTitleBlock )
|
|
{
|
|
m_pcb->SetTitleBlock( aTitleBlock );
|
|
}
|
|
|
|
|
|
BOARD_DESIGN_SETTINGS& PCB_BASE_FRAME::GetDesignSettings() const
|
|
{
|
|
return m_pcb->GetDesignSettings();
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::SetDrawBgColor( const COLOR4D& aColor )
|
|
{
|
|
m_drawBgColor= aColor;
|
|
m_auimgr.Update();
|
|
}
|
|
|
|
|
|
const ZONE_SETTINGS& PCB_BASE_FRAME::GetZoneSettings() const
|
|
{
|
|
return m_pcb->GetDesignSettings().GetDefaultZoneSettings();
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::SetZoneSettings( const ZONE_SETTINGS& aSettings )
|
|
{
|
|
m_pcb->GetDesignSettings().SetDefaultZoneSettings( aSettings );
|
|
}
|
|
|
|
|
|
const PCB_PLOT_PARAMS& PCB_BASE_FRAME::GetPlotSettings() const
|
|
{
|
|
return m_pcb->GetPlotOptions();
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::SetPlotSettings( const PCB_PLOT_PARAMS& aSettings )
|
|
{
|
|
m_pcb->SetPlotOptions( aSettings );
|
|
}
|
|
|
|
|
|
BOX2I PCB_BASE_FRAME::GetBoardBoundingBox( bool aBoardEdgesOnly ) const
|
|
{
|
|
BOX2I area = aBoardEdgesOnly ? m_pcb->GetBoardEdgesBoundingBox() : m_pcb->GetBoundingBox();
|
|
|
|
if( area.GetWidth() == 0 && area.GetHeight() == 0 )
|
|
{
|
|
VECTOR2I pageSize = GetPageSizeIU();
|
|
|
|
if( m_showBorderAndTitleBlock )
|
|
{
|
|
area.SetOrigin( 0, 0 );
|
|
area.SetEnd( pageSize.x, pageSize.y );
|
|
}
|
|
else
|
|
{
|
|
area.SetOrigin( -pageSize.x / 2, -pageSize.y / 2 );
|
|
area.SetEnd( pageSize.x / 2, pageSize.y / 2 );
|
|
}
|
|
}
|
|
|
|
return area;
|
|
}
|
|
|
|
|
|
// Virtual function
|
|
void PCB_BASE_FRAME::doReCreateMenuBar()
|
|
{
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::ShowChangedLanguage()
|
|
{
|
|
// call my base class
|
|
EDA_DRAW_FRAME::ShowChangedLanguage();
|
|
|
|
// tooltips in toolbars
|
|
RecreateToolbars();
|
|
}
|
|
|
|
|
|
EDA_3D_VIEWER_FRAME* PCB_BASE_FRAME::CreateAndShow3D_Frame()
|
|
{
|
|
EDA_3D_VIEWER_FRAME* draw3DFrame = Get3DViewerFrame();
|
|
|
|
if( !draw3DFrame )
|
|
draw3DFrame = new EDA_3D_VIEWER_FRAME( &Kiway(), this, _( "3D Viewer" ) );
|
|
|
|
// Raising the window does not show the window on Windows if iconized. This should work
|
|
// on any platform.
|
|
if( draw3DFrame->IsIconized() )
|
|
draw3DFrame->Iconize( false );
|
|
|
|
draw3DFrame->Raise();
|
|
draw3DFrame->Show( true );
|
|
|
|
// Raising the window does not set the focus on Linux. This should work on any platform.
|
|
if( wxWindow::FindFocus() != draw3DFrame )
|
|
draw3DFrame->SetFocus();
|
|
|
|
// Allocate a slice of time to display the 3D frame
|
|
// a call to wxSafeYield() should be enough (and better), but on Linux we need
|
|
// to call wxYield()
|
|
// otherwise the activity messages are not displayed during the first board loading
|
|
wxYield();
|
|
|
|
// Note, the caller is responsible to load/update the board 3D view.
|
|
// after frame creation the board is not automatically created.
|
|
|
|
return draw3DFrame;
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::SwitchLayer( PCB_LAYER_ID layer )
|
|
{
|
|
PCB_LAYER_ID preslayer = GetActiveLayer();
|
|
auto& displ_opts = GetDisplayOptions();
|
|
|
|
// Check if the specified layer matches the present layer
|
|
if( layer == preslayer )
|
|
return;
|
|
|
|
// Copper layers cannot be selected unconditionally; how many of those layers are
|
|
// currently enabled needs to be checked.
|
|
if( IsCopperLayer( layer ) )
|
|
{
|
|
// If only one copper layer is enabled, the only such layer that can be selected to
|
|
// is the "Copper" layer (so the selection of any other copper layer is disregarded).
|
|
if( m_pcb->GetCopperLayerCount() < 2 )
|
|
{
|
|
if( layer != B_Cu )
|
|
return;
|
|
}
|
|
|
|
// If more than one copper layer is enabled, the "Copper" and "Component" layers
|
|
// can be selected, but the total number of copper layers determines which internal
|
|
// layers are also capable of being selected.
|
|
else
|
|
{
|
|
if( layer != B_Cu && layer != F_Cu && layer >= ( m_pcb->GetCopperLayerCount() - 1 ) )
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Is yet more checking required? E.g. when the layer to be selected is a non-copper
|
|
// layer, or when switching between a copper layer and a non-copper layer, or vice-versa?
|
|
// ...
|
|
|
|
SetActiveLayer( layer );
|
|
|
|
if( displ_opts.m_ContrastModeDisplay != HIGH_CONTRAST_MODE::NORMAL )
|
|
GetCanvas()->Refresh();
|
|
}
|
|
|
|
|
|
GENERAL_COLLECTORS_GUIDE PCB_BASE_FRAME::GetCollectorsGuide()
|
|
{
|
|
GENERAL_COLLECTORS_GUIDE guide( m_pcb->GetVisibleLayers(), GetActiveLayer(),
|
|
GetCanvas()->GetView() );
|
|
|
|
// account for the globals
|
|
guide.SetIgnoreMTextsMarkedNoShow( ! m_pcb->IsElementVisible( LAYER_MOD_TEXT_INVISIBLE ) );
|
|
guide.SetIgnoreMTextsOnBack( ! m_pcb->IsElementVisible( LAYER_MOD_TEXT ) );
|
|
guide.SetIgnoreMTextsOnFront( ! m_pcb->IsElementVisible( LAYER_MOD_TEXT ) );
|
|
guide.SetIgnoreModulesOnBack( ! m_pcb->IsElementVisible( LAYER_MOD_BK ) );
|
|
guide.SetIgnoreModulesOnFront( ! m_pcb->IsElementVisible( LAYER_MOD_FR ) );
|
|
guide.SetIgnorePadsOnBack( ! m_pcb->IsElementVisible( LAYER_PAD_BK ) );
|
|
guide.SetIgnorePadsOnFront( ! m_pcb->IsElementVisible( LAYER_PAD_FR ) );
|
|
guide.SetIgnoreThroughHolePads( ! m_pcb->IsElementVisible( LAYER_PADS_TH ) );
|
|
guide.SetIgnoreModulesVals( ! m_pcb->IsElementVisible( LAYER_MOD_VALUES ) );
|
|
guide.SetIgnoreModulesRefs( ! m_pcb->IsElementVisible( LAYER_MOD_REFERENCES ) );
|
|
guide.SetIgnoreThroughVias( ! m_pcb->IsElementVisible( LAYER_VIAS ) );
|
|
guide.SetIgnoreBlindBuriedVias( ! m_pcb->IsElementVisible( LAYER_VIAS ) );
|
|
guide.SetIgnoreMicroVias( ! m_pcb->IsElementVisible( LAYER_VIAS ) );
|
|
guide.SetIgnoreTracks( ! m_pcb->IsElementVisible( LAYER_TRACKS ) );
|
|
|
|
return guide;
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::DisplayGridMsg()
|
|
{
|
|
VECTOR2D gridSize = GetCanvas()->GetGAL()->GetGridSize();
|
|
wxString line;
|
|
|
|
line.Printf( wxT( "grid X %s Y %s" ),
|
|
MessageTextFromValue( gridSize.x, false ),
|
|
MessageTextFromValue( gridSize.y, false ) );
|
|
|
|
SetStatusText( line, 4 );
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::UpdateStatusBar()
|
|
{
|
|
EDA_DRAW_FRAME::UpdateStatusBar();
|
|
|
|
BASE_SCREEN* screen = GetScreen();
|
|
|
|
if( !screen )
|
|
return;
|
|
|
|
wxString line;
|
|
VECTOR2D cursorPos = GetCanvas()->GetViewControls()->GetCursorPosition();
|
|
|
|
if( GetShowPolarCoords() ) // display polar coordinates
|
|
{
|
|
double dx = cursorPos.x - screen->m_LocalOrigin.x;
|
|
double dy = cursorPos.y - screen->m_LocalOrigin.y;
|
|
double theta = RAD2DEG( atan2( -dy, dx ) );
|
|
double ro = hypot( dx, dy );
|
|
|
|
line.Printf( wxT( "r %s theta %.3f" ),
|
|
MessageTextFromValue( ro, false ),
|
|
theta );
|
|
|
|
SetStatusText( line, 3 );
|
|
}
|
|
|
|
// Transform absolute coordinates for user origin preferences
|
|
double userXpos = m_originTransforms.ToDisplayAbsX( static_cast<double>( cursorPos.x ) );
|
|
double userYpos = m_originTransforms.ToDisplayAbsY( static_cast<double>( cursorPos.y ) );
|
|
|
|
// Display absolute coordinates:
|
|
line.Printf( wxT( "X %s Y %s" ),
|
|
MessageTextFromValue( userXpos, false ),
|
|
MessageTextFromValue( userYpos, false ) );
|
|
SetStatusText( line, 2 );
|
|
|
|
if( !GetShowPolarCoords() ) // display relative cartesian coordinates
|
|
{
|
|
// Calculate relative coordinates
|
|
double relXpos = cursorPos.x - screen->m_LocalOrigin.x;
|
|
double relYpos = cursorPos.y - screen->m_LocalOrigin.y;
|
|
|
|
// Transform relative coordinates for user origin preferences
|
|
userXpos = m_originTransforms.ToDisplayRelX( relXpos );
|
|
userYpos = m_originTransforms.ToDisplayRelY( relYpos );
|
|
|
|
line.Printf( wxT( "dx %s dy %s dist %s" ),
|
|
MessageTextFromValue( userXpos, false ),
|
|
MessageTextFromValue( userYpos, false ),
|
|
MessageTextFromValue( hypot( userXpos, userYpos ), false ) );
|
|
SetStatusText( line, 3 );
|
|
}
|
|
|
|
DisplayGridMsg();
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::unitsChangeRefresh()
|
|
{
|
|
EDA_DRAW_FRAME::unitsChangeRefresh(); // Update the status bar.
|
|
|
|
if( GetBoard() )
|
|
GetBoard()->SetUserUnits( GetUserUnits() );
|
|
|
|
UpdateGridSelectBox();
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg )
|
|
{
|
|
EDA_DRAW_FRAME::LoadSettings( aCfg );
|
|
|
|
if( aCfg->m_Window.grid.sizes.empty() )
|
|
aCfg->m_Window.grid.sizes = aCfg->DefaultGridSizeList();
|
|
|
|
// Currently values read from config file are not used because the user cannot
|
|
// change this config
|
|
// if( aCfg->m_Window.zoom_factors.empty() )
|
|
{
|
|
aCfg->m_Window.zoom_factors = { ZOOM_LIST_PCBNEW };
|
|
}
|
|
|
|
// Some, but not all, derived classes have a PCBNEW_SETTINGS.
|
|
if( PCBNEW_SETTINGS* pcbnew_cfg = dynamic_cast<PCBNEW_SETTINGS*>( aCfg ) )
|
|
m_polarCoords = pcbnew_cfg->m_PolarCoords;
|
|
|
|
wxASSERT( GetCanvas() );
|
|
|
|
if( GetCanvas() )
|
|
{
|
|
RENDER_SETTINGS* rs = GetCanvas()->GetView()->GetPainter()->GetSettings();
|
|
|
|
if( rs )
|
|
{
|
|
rs->SetHighlightFactor( aCfg->m_Graphics.highlight_factor );
|
|
rs->SetSelectFactor( aCfg->m_Graphics.select_factor );
|
|
rs->SetDefaultFont( wxEmptyString ); // Always the KiCad font for PCBs
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
SEVERITY PCB_BASE_FRAME::GetSeverity( int aErrorCode ) const
|
|
{
|
|
if( aErrorCode >= CLEANUP_FIRST )
|
|
return RPT_SEVERITY_ACTION;
|
|
|
|
BOARD_DESIGN_SETTINGS& bds = GetBoard()->GetDesignSettings();
|
|
|
|
return bds.m_DRCSeverities[ aErrorCode ];
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg )
|
|
{
|
|
EDA_DRAW_FRAME::SaveSettings( aCfg );
|
|
|
|
// Some, but not all derived classes have a PCBNEW_SETTINGS.
|
|
PCBNEW_SETTINGS* cfg = dynamic_cast<PCBNEW_SETTINGS*>( aCfg );
|
|
|
|
if( cfg )
|
|
cfg->m_PolarCoords = m_polarCoords;
|
|
}
|
|
|
|
|
|
PCBNEW_SETTINGS* PCB_BASE_FRAME::GetPcbNewSettings() const
|
|
{
|
|
return Pgm().GetSettingsManager().GetAppSettings<PCBNEW_SETTINGS>();
|
|
}
|
|
|
|
|
|
FOOTPRINT_EDITOR_SETTINGS* PCB_BASE_FRAME::GetFootprintEditorSettings() const
|
|
{
|
|
return Pgm().GetSettingsManager().GetAppSettings<FOOTPRINT_EDITOR_SETTINGS>();
|
|
}
|
|
|
|
|
|
PCB_VIEWERS_SETTINGS_BASE* PCB_BASE_FRAME::GetViewerSettingsBase() const
|
|
{
|
|
switch( GetFrameType() )
|
|
{
|
|
case FRAME_PCB_EDITOR:
|
|
case FRAME_FOOTPRINT_EDITOR:
|
|
case FRAME_FOOTPRINT_WIZARD:
|
|
case FRAME_PCB_DISPLAY3D:
|
|
default:
|
|
return Pgm().GetSettingsManager().GetAppSettings<PCBNEW_SETTINGS>();
|
|
|
|
case FRAME_FOOTPRINT_VIEWER:
|
|
case FRAME_FOOTPRINT_VIEWER_MODAL:
|
|
case FRAME_FOOTPRINT_PREVIEW:
|
|
case FRAME_CVPCB:
|
|
case FRAME_CVPCB_DISPLAY:
|
|
return Pgm().GetSettingsManager().GetAppSettings<CVPCB_SETTINGS>();
|
|
}
|
|
}
|
|
|
|
|
|
MAGNETIC_SETTINGS* PCB_BASE_FRAME::GetMagneticItemsSettings()
|
|
{
|
|
return &GetPcbNewSettings()->m_MagneticItems;
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::CommonSettingsChanged( bool aEnvVarsChanged, bool aTextVarsChanged )
|
|
{
|
|
EDA_DRAW_FRAME::CommonSettingsChanged( aEnvVarsChanged, aTextVarsChanged );
|
|
|
|
RENDER_SETTINGS* settings = GetCanvas()->GetView()->GetPainter()->GetSettings();
|
|
PCB_RENDER_SETTINGS* renderSettings = static_cast<KIGFX::PCB_RENDER_SETTINGS*>( settings );
|
|
|
|
renderSettings->LoadColors( GetColorSettings( true ) );
|
|
renderSettings->LoadDisplayOptions( GetDisplayOptions() );
|
|
|
|
// 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.
|
|
GetCanvas()->GetView()->UpdateAllItemsConditionally(
|
|
[&]( KIGFX::VIEW_ITEM* aItem ) -> int
|
|
{
|
|
if( dynamic_cast<RATSNEST_VIEW_ITEM*>( aItem ) )
|
|
{
|
|
return KIGFX::ALL; // ratsnest display
|
|
}
|
|
else if( dynamic_cast<PCB_TRACK*>( aItem ) )
|
|
{
|
|
return KIGFX::REPAINT; // track, arc & via clearance display
|
|
}
|
|
else if( dynamic_cast<PAD*>( aItem ) )
|
|
{
|
|
return KIGFX::REPAINT; // pad clearance display
|
|
}
|
|
|
|
return 0;
|
|
} );
|
|
|
|
GetCanvas()->GetView()->UpdateAllItems( KIGFX::COLOR );
|
|
|
|
RecreateToolbars();
|
|
|
|
// The 3D viewer isn't in the Kiway, so send its update manually
|
|
EDA_3D_VIEWER_FRAME* viewer = Get3DViewerFrame();
|
|
|
|
if( viewer )
|
|
viewer->CommonSettingsChanged( aEnvVarsChanged, aTextVarsChanged );
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::OnModify()
|
|
{
|
|
EDA_BASE_FRAME::OnModify();
|
|
|
|
GetScreen()->SetContentModified();
|
|
m_autoSaveRequired = true;
|
|
|
|
GetBoard()->IncrementTimeStamp();
|
|
|
|
UpdateStatusBar();
|
|
UpdateMsgPanel();
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::rebuildConnectivity()
|
|
{
|
|
GetBoard()->BuildConnectivity();
|
|
GetToolManager()->PostEvent( EVENTS::ConnectivityChangedEvent );
|
|
GetCanvas()->RedrawRatsnest();
|
|
}
|
|
|
|
|
|
PCB_DRAW_PANEL_GAL* PCB_BASE_FRAME::GetCanvas() const
|
|
{
|
|
return static_cast<PCB_DRAW_PANEL_GAL*>( EDA_DRAW_FRAME::GetCanvas() );
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::ActivateGalCanvas()
|
|
{
|
|
EDA_DRAW_FRAME::ActivateGalCanvas();
|
|
|
|
EDA_DRAW_PANEL_GAL* canvas = GetCanvas();
|
|
KIGFX::VIEW* view = canvas->GetView();
|
|
|
|
if( m_toolManager )
|
|
{
|
|
m_toolManager->SetEnvironment( m_pcb, view, canvas->GetViewControls(), config(), this );
|
|
|
|
m_toolManager->ResetTools( TOOL_BASE::GAL_SWITCH );
|
|
}
|
|
|
|
KIGFX::PCB_PAINTER* painter = static_cast<KIGFX::PCB_PAINTER*>( view->GetPainter() );
|
|
KIGFX::PCB_RENDER_SETTINGS* settings = painter->GetSettings();
|
|
const PCB_DISPLAY_OPTIONS& displ_opts = GetDisplayOptions();
|
|
|
|
settings->LoadDisplayOptions( displ_opts );
|
|
settings->LoadColors( GetColorSettings() );
|
|
|
|
view->RecacheAllItems();
|
|
canvas->SetEventDispatcher( m_toolDispatcher );
|
|
canvas->StartDrawing();
|
|
|
|
if( ADVANCED_CFG::GetCfg().m_Use3DConnexionDriver )
|
|
{
|
|
try
|
|
|
|
{
|
|
if( m_spaceMouse == nullptr )
|
|
{
|
|
m_spaceMouse = new NL_PCBNEW_PLUGIN( GetCanvas() );
|
|
}
|
|
}
|
|
catch( const std::system_error& e )
|
|
{
|
|
wxLogTrace( wxT( "KI_TRACE_NAVLIB" ), e.what() );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::SetDisplayOptions( const PCB_DISPLAY_OPTIONS& aOptions, bool aRefresh )
|
|
{
|
|
bool hcChanged = m_displayOptions.m_ContrastModeDisplay != aOptions.m_ContrastModeDisplay;
|
|
bool hcVisChanged = m_displayOptions.m_ContrastModeDisplay == HIGH_CONTRAST_MODE::HIDDEN
|
|
|| aOptions.m_ContrastModeDisplay == HIGH_CONTRAST_MODE::HIDDEN;
|
|
m_displayOptions = aOptions;
|
|
|
|
EDA_DRAW_PANEL_GAL* canvas = GetCanvas();
|
|
KIGFX::PCB_VIEW* view = static_cast<KIGFX::PCB_VIEW*>( canvas->GetView() );
|
|
|
|
view->UpdateDisplayOptions( aOptions );
|
|
canvas->SetHighContrastLayer( GetActiveLayer() );
|
|
OnDisplayOptionsChanged();
|
|
|
|
// Vias on a restricted layer set must be redrawn when high contrast mode is changed
|
|
if( hcChanged )
|
|
{
|
|
// 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.
|
|
GetCanvas()->GetView()->UpdateAllItemsConditionally(
|
|
[&]( KIGFX::VIEW_ITEM* aItem ) -> int
|
|
{
|
|
if( PCB_VIA* via = dynamic_cast<PCB_VIA*>( aItem ) )
|
|
{
|
|
if( via->GetViaType() == VIATYPE::BLIND_BURIED
|
|
|| via->GetViaType() == VIATYPE::MICROVIA
|
|
|| via->GetRemoveUnconnected() )
|
|
{
|
|
return hcVisChanged ? KIGFX::ALL : KIGFX::REPAINT;
|
|
}
|
|
}
|
|
else if( PAD* pad = dynamic_cast<PAD*>( aItem ) )
|
|
{
|
|
if( pad->GetRemoveUnconnected() )
|
|
return hcVisChanged ? KIGFX::ALL : KIGFX::REPAINT;
|
|
}
|
|
|
|
return 0;
|
|
} );
|
|
}
|
|
|
|
if( aRefresh )
|
|
canvas->Refresh();
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::setFPWatcher( FOOTPRINT* aFootprint )
|
|
{
|
|
Unbind( wxEVT_FSWATCHER, &PCB_BASE_FRAME::OnFPChange, this );
|
|
|
|
if( !aFootprint )
|
|
{
|
|
m_watcher.reset();
|
|
return;
|
|
}
|
|
|
|
wxString libfullname;
|
|
FP_LIB_TABLE* tbl = Prj().PcbFootprintLibs();
|
|
|
|
if( !aFootprint || !tbl )
|
|
return;
|
|
|
|
try
|
|
{
|
|
const FP_LIB_TABLE_ROW* row = tbl->FindRow( aFootprint->GetFPID().GetLibNickname() );
|
|
|
|
if( !row )
|
|
return;
|
|
|
|
libfullname = row->GetFullURI( true );
|
|
}
|
|
catch( const std::exception& e )
|
|
{
|
|
DisplayInfoMessage( this, e.what() );
|
|
return;
|
|
}
|
|
catch( const IO_ERROR& error )
|
|
{
|
|
wxLogTrace( "KICAD_LIB_WATCH", "Error: %s", error.What() );
|
|
return;
|
|
}
|
|
|
|
m_watcherFileName.Assign( libfullname, aFootprint->GetFPID().GetLibItemName(),
|
|
KiCadFootprintFileExtension );
|
|
|
|
if( !m_watcherFileName.FileExists() )
|
|
return;
|
|
|
|
m_watcherLastModified = m_watcherFileName.GetModificationTime();
|
|
|
|
Bind( wxEVT_FSWATCHER, &PCB_BASE_FRAME::OnFPChange, this );
|
|
m_watcher = std::make_unique<wxFileSystemWatcher>();
|
|
m_watcher->SetOwner( this );
|
|
|
|
wxFileName fn;
|
|
fn.AssignDir( m_watcherFileName.GetPath() );
|
|
fn.DontFollowLink();
|
|
|
|
m_watcher->AddTree( fn );
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::OnFPChange( wxFileSystemWatcherEvent& aEvent )
|
|
{
|
|
if( aEvent.GetPath() != m_watcherFileName.GetFullPath() )
|
|
return;
|
|
|
|
// Start the debounce timer (set to 1 second)
|
|
if( !m_watcherDebounceTimer.StartOnce( 1000 ) )
|
|
{
|
|
wxLogTrace( "KICAD_LIB_WATCH", "Failed to start the debounce timer" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
void PCB_BASE_FRAME::OnFpChangeDebounceTimer( wxTimerEvent& aEvent )
|
|
{
|
|
wxLogTrace( "KICAD_LIB_WATCH", "OnFpChangeDebounceTimer" );
|
|
|
|
// Disable logging to avoid spurious messages and check if the file has changed
|
|
wxLog::EnableLogging( false );
|
|
wxDateTime lastModified = m_watcherFileName.GetModificationTime();
|
|
wxLog::EnableLogging( true );
|
|
|
|
if( lastModified == m_watcherLastModified || !lastModified.IsValid() )
|
|
return;
|
|
|
|
m_watcherLastModified = lastModified;
|
|
|
|
FOOTPRINT* fp = GetBoard()->GetFirstFootprint();
|
|
FP_LIB_TABLE* tbl = Prj().PcbFootprintLibs();
|
|
|
|
if( !fp || !tbl )
|
|
return;
|
|
|
|
if( !GetScreen()->IsContentModified()
|
|
|| IsOK( this, _( "The library containing the current footprint has changed.\n"
|
|
"Do you want to reload the footprint?" ) ) )
|
|
{
|
|
wxString fpname = fp->GetFPID().GetLibItemName();
|
|
wxString nickname = fp->GetFPID().GetLibNickname();
|
|
|
|
try
|
|
{
|
|
FOOTPRINT* newfp = tbl->FootprintLoad( nickname, fpname );
|
|
|
|
if( newfp )
|
|
ReloadFootprint( newfp );
|
|
}
|
|
catch( const IO_ERROR& ioe )
|
|
{
|
|
DisplayError( this, ioe.What() );
|
|
}
|
|
}
|
|
}
|