2014-03-24 08:45:05 +01:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
2016-05-04 14:59:14 +02:00
|
|
|
* Copyright (C) 2014-2016 CERN
|
2025-01-01 13:30:11 -08:00
|
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
2014-03-24 08:45:05 +01:00
|
|
|
* @author Maciej Suminski <maciej.suminski@cern.ch>
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2025-01-07 19:58:40 +08:00
|
|
|
#include "pcb_control.h"
|
2025-03-27 10:13:31 +00:00
|
|
|
#include "convert_basic_shapes_to_polygon.h"
|
2025-01-07 19:58:40 +08:00
|
|
|
|
2023-09-18 06:13:09 +03:00
|
|
|
#include <kiplatform/ui.h>
|
2022-02-20 13:53:34 +00:00
|
|
|
#include <tools/edit_tool.h>
|
2022-07-15 16:14:11 +01:00
|
|
|
#include <tools/board_inspection_tool.h>
|
2022-02-20 13:53:34 +00:00
|
|
|
#include <router/router_tool.h>
|
2021-07-27 15:13:26 +01:00
|
|
|
#include <pgm_base.h>
|
2022-02-20 13:53:34 +00:00
|
|
|
#include <tools/pcb_actions.h>
|
|
|
|
#include <tools/pcb_picker_tool.h>
|
|
|
|
#include <tools/pcb_selection_tool.h>
|
|
|
|
#include <tools/board_reannotate_tool.h>
|
2021-08-05 13:03:59 +01:00
|
|
|
#include <3d_viewer/eda_3d_viewer_frame.h>
|
2019-06-11 07:47:54 -07:00
|
|
|
#include <board_commit.h>
|
2020-11-12 20:19:22 +00:00
|
|
|
#include <board.h>
|
2021-06-06 15:03:10 -04:00
|
|
|
#include <board_design_settings.h>
|
2020-11-14 18:11:28 +00:00
|
|
|
#include <board_item.h>
|
2025-08-07 02:38:07 +08:00
|
|
|
#include <board_stackup_manager/stackup_predefined_prms.h>
|
2024-10-13 16:32:20 +08:00
|
|
|
#include <clipboard.h>
|
2025-03-20 11:57:59 -04:00
|
|
|
#include <design_block.h>
|
2021-05-01 23:44:22 +01:00
|
|
|
#include <dialogs/dialog_paste_special.h>
|
2021-06-11 17:59:28 +01:00
|
|
|
#include <pcb_dimension.h>
|
2025-08-19 13:57:07 -04:00
|
|
|
#include <geometry/convex_hull.h>
|
|
|
|
#include <geometry/shape_utils.h>
|
2023-09-18 19:52:27 -04:00
|
|
|
#include <gal/graphics_abstraction_layer.h>
|
2020-11-12 20:19:22 +00:00
|
|
|
#include <footprint.h>
|
2024-07-31 19:31:07 +08:00
|
|
|
#include <layer_pairs.h>
|
2021-06-03 18:03:25 +01:00
|
|
|
#include <pcb_group.h>
|
2024-07-31 19:31:07 +08:00
|
|
|
#include <pcb_layer_presentation.h>
|
2024-10-13 16:32:20 +08:00
|
|
|
#include <pcb_reference_image.h>
|
2022-01-30 10:52:52 +00:00
|
|
|
#include <pcb_textbox.h>
|
2021-06-11 22:07:02 +01:00
|
|
|
#include <pcb_track.h>
|
2024-01-16 23:37:12 +00:00
|
|
|
#include <pcb_generator.h>
|
2025-07-20 18:57:59 +01:00
|
|
|
#include <project_pcb.h>
|
2022-09-14 22:28:09 +00:00
|
|
|
#include <wildcards_and_files_ext.h>
|
2025-08-10 20:19:32 -07:00
|
|
|
#include <filename_resolver.h>
|
|
|
|
#include <3d_cache/3d_cache.h>
|
|
|
|
#include <embedded_files.h>
|
|
|
|
#include <wx/filename.h>
|
2020-11-11 23:05:59 +00:00
|
|
|
#include <zone.h>
|
2014-03-24 18:28:21 +01:00
|
|
|
#include <confirm.h>
|
2024-04-27 22:57:24 +03:00
|
|
|
#include <kidialog.h>
|
2019-06-11 07:47:54 -07:00
|
|
|
#include <connectivity/connectivity_data.h>
|
2020-10-24 08:53:11 -04:00
|
|
|
#include <core/kicad_algo.h>
|
2023-06-17 23:47:26 -04:00
|
|
|
#include <dialogs/hotkey_cycle_popup.h>
|
2017-04-15 18:08:23 +02:00
|
|
|
#include <kicad_clipboard.h>
|
2019-06-11 07:47:54 -07:00
|
|
|
#include <origin_viewitem.h>
|
|
|
|
#include <pcb_edit_frame.h>
|
|
|
|
#include <pcb_painter.h>
|
2020-01-12 20:44:19 -05:00
|
|
|
#include <settings/color_settings.h>
|
2023-04-17 15:39:34 +00:00
|
|
|
#include <string>
|
2015-06-18 17:51:52 +02:00
|
|
|
#include <tool/tool_manager.h>
|
2025-08-19 13:57:07 -04:00
|
|
|
#include <tools/multichannel_tool.h>
|
2022-09-14 22:28:09 +00:00
|
|
|
#include <footprint_edit_frame.h>
|
2023-09-16 22:55:29 -04:00
|
|
|
#include <footprint_editor_settings.h>
|
2025-02-20 16:21:40 +00:00
|
|
|
#include <footprint_viewer_frame.h>
|
2023-12-31 17:08:09 +00:00
|
|
|
#include <widgets/appearance_controls.h>
|
2025-03-20 11:57:59 -04:00
|
|
|
#include <widgets/pcb_design_block_pane.h>
|
2021-08-14 21:05:21 +01:00
|
|
|
#include <widgets/wx_progress_reporters.h>
|
2022-12-28 22:03:03 +00:00
|
|
|
#include <widgets/wx_infobar.h>
|
2021-07-27 15:13:26 +01:00
|
|
|
#include <wx/hyperlink.h>
|
2019-06-04 19:46:52 +01:00
|
|
|
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2016-06-29 12:23:11 +02:00
|
|
|
using namespace std::placeholders;
|
2015-06-18 17:51:51 +02:00
|
|
|
|
2014-07-09 16:57:01 +02:00
|
|
|
|
2015-08-07 18:24:42 +02:00
|
|
|
// files.cpp
|
2023-08-13 05:26:06 +03:00
|
|
|
extern bool AskLoadBoardFileName( PCB_EDIT_FRAME* aParent, wxString* aFileName, int aCtl = 0 );
|
|
|
|
|
2015-08-07 18:24:42 +02:00
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
PCB_CONTROL::PCB_CONTROL() :
|
2019-12-23 18:11:45 +01:00
|
|
|
PCB_TOOL_BASE( "pcbnew.Control" ),
|
2020-01-13 00:17:50 +00:00
|
|
|
m_frame( nullptr ),
|
|
|
|
m_pickerItem( nullptr )
|
2014-03-24 08:45:05 +01:00
|
|
|
{
|
2017-03-02 23:57:13 +01:00
|
|
|
m_gridOrigin.reset( new KIGFX::ORIGIN_VIEWITEM() );
|
2015-06-18 17:51:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
PCB_CONTROL::~PCB_CONTROL()
|
2015-06-18 17:51:53 +02:00
|
|
|
{
|
2014-03-24 08:45:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
void PCB_CONTROL::Reset( RESET_REASON aReason )
|
2014-03-24 17:20:23 +01:00
|
|
|
{
|
2014-07-09 13:50:27 +02:00
|
|
|
m_frame = getEditFrame<PCB_BASE_FRAME>();
|
2015-06-18 17:51:53 +02:00
|
|
|
|
2022-12-26 08:23:44 -08:00
|
|
|
if( aReason == MODEL_RELOAD || aReason == GAL_SWITCH || aReason == REDRAW )
|
2015-06-18 17:51:53 +02:00
|
|
|
{
|
2022-08-30 15:13:51 +01:00
|
|
|
m_gridOrigin->SetPosition( board()->GetDesignSettings().GetGridOrigin() );
|
2025-07-23 18:35:24 +01:00
|
|
|
|
|
|
|
double backgroundBrightness = m_frame->GetCanvas()->GetGAL()->GetClearColor().GetBrightness();
|
|
|
|
COLOR4D color = m_frame->GetGridColor();
|
|
|
|
|
|
|
|
if( backgroundBrightness > 0.5 )
|
|
|
|
color.Darken( 0.25 );
|
|
|
|
else
|
|
|
|
color.Brighten( 0.25 );
|
|
|
|
|
|
|
|
m_gridOrigin->SetColor( color );
|
|
|
|
|
2017-03-02 23:57:13 +01:00
|
|
|
getView()->Remove( m_gridOrigin.get() );
|
|
|
|
getView()->Add( m_gridOrigin.get() );
|
2015-06-18 17:51:53 +02:00
|
|
|
}
|
2014-03-24 17:20:23 +01:00
|
|
|
}
|
|
|
|
|
2019-05-26 16:36:40 +01:00
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::AddLibrary( const TOOL_EVENT& aEvent )
|
2019-06-02 21:07:57 +01:00
|
|
|
{
|
2019-09-05 23:00:47 +01:00
|
|
|
if( m_frame->IsType( FRAME_FOOTPRINT_EDITOR ) || m_frame->IsType( FRAME_PCB_EDITOR ) )
|
2019-06-02 21:07:57 +01:00
|
|
|
{
|
|
|
|
if( aEvent.IsAction( &ACTIONS::newLibrary ) )
|
2025-07-20 18:57:59 +01:00
|
|
|
static_cast<PCB_BASE_EDIT_FRAME*>( m_frame )->CreateNewLibrary( _( "New Footprint Library" ) );
|
2019-06-02 21:07:57 +01:00
|
|
|
else if( aEvent.IsAction( &ACTIONS::addLibrary ) )
|
2025-07-20 18:57:59 +01:00
|
|
|
static_cast<PCB_BASE_EDIT_FRAME*>( m_frame )->AddLibrary( _( "Add Footprint Library" ) );
|
2019-06-02 21:07:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-20 16:21:40 +00:00
|
|
|
int PCB_CONTROL::LoadFpFromBoard( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
if( m_frame->IsType( FRAME_FOOTPRINT_EDITOR ) )
|
|
|
|
static_cast<FOOTPRINT_EDIT_FRAME*>( m_frame )->LoadFootprintFromBoard( nullptr );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PCB_CONTROL::SaveFpToBoard( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
if( m_frame->IsType( FRAME_FOOTPRINT_EDITOR ) )
|
|
|
|
static_cast<FOOTPRINT_EDIT_FRAME*>( m_frame )->SaveFootprintToBoard( true );
|
|
|
|
else if( m_frame->IsType( FRAME_FOOTPRINT_VIEWER ) )
|
|
|
|
static_cast<FOOTPRINT_VIEWER_FRAME*>( m_frame )->AddFootprintToPCB();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-09-14 22:28:09 +00:00
|
|
|
int PCB_CONTROL::DdAddLibrary( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
const wxString fn = *aEvent.Parameter<wxString*>();
|
2025-07-20 18:57:59 +01:00
|
|
|
static_cast<PCB_BASE_EDIT_FRAME*>( m_frame )->AddLibrary( _( "Add Footprint Library" ), fn,
|
|
|
|
PROJECT_PCB::PcbFootprintLibs( &m_frame->Prj() ) );
|
2022-09-14 22:28:09 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PCB_CONTROL::DdImportFootprint( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
const wxString fn = *aEvent.Parameter<wxString*>();
|
|
|
|
static_cast<FOOTPRINT_EDIT_FRAME*>( m_frame )->ImportFootprint( fn );
|
|
|
|
m_frame->Zoom_Automatique( false );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-20 16:21:40 +00:00
|
|
|
int PCB_CONTROL::IterateFootprint( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
if( m_frame->IsType( FRAME_FOOTPRINT_VIEWER ) )
|
|
|
|
static_cast<FOOTPRINT_VIEWER_FRAME*>( m_frame )->SelectAndViewFootprint( aEvent.Parameter<FPVIEWER_CONSTANTS>() );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::Quit( const TOOL_EVENT& aEvent )
|
2019-05-26 16:36:40 +01:00
|
|
|
{
|
|
|
|
m_frame->Close( false );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-10-30 20:17:23 +01:00
|
|
|
template<class T> void Flip( T& aValue )
|
|
|
|
{
|
|
|
|
aValue = !aValue;
|
|
|
|
}
|
2014-03-24 17:20:23 +01:00
|
|
|
|
2020-06-12 11:58:56 +01:00
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::TrackDisplayMode( const TOOL_EVENT& aEvent )
|
2014-03-24 08:45:05 +01:00
|
|
|
{
|
2021-12-26 00:36:12 +00:00
|
|
|
Flip( displayOptions().m_DisplayPcbTrackFill );
|
2014-03-24 17:20:23 +01:00
|
|
|
|
2021-09-13 17:18:58 +01:00
|
|
|
for( PCB_TRACK* track : board()->Tracks() )
|
2014-07-09 15:02:56 +02:00
|
|
|
{
|
2020-10-21 19:43:14 +02:00
|
|
|
if( track->Type() == PCB_TRACE_T || track->Type() == PCB_ARC_T )
|
2021-04-08 17:35:51 -04:00
|
|
|
view()->Update( track, KIGFX::REPAINT );
|
2014-07-09 15:02:56 +02:00
|
|
|
}
|
2014-03-24 17:20:23 +01:00
|
|
|
|
2023-07-30 15:39:07 -04:00
|
|
|
for( BOARD_ITEM* shape : board()->Drawings() )
|
|
|
|
{
|
|
|
|
if( shape->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( shape )->IsOnCopperLayer() )
|
|
|
|
view()->Update( shape, KIGFX::REPAINT );
|
|
|
|
}
|
|
|
|
|
2017-10-30 20:17:23 +01:00
|
|
|
canvas()->Refresh();
|
2014-03-24 08:45:05 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-05-15 23:49:48 +01:00
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::ToggleRatsnest( const TOOL_EVENT& aEvent )
|
2018-04-30 09:52:49 +02:00
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
if( PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame ) )
|
2019-05-15 23:49:48 +01:00
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
if( aEvent.IsAction( &PCB_ACTIONS::showRatsnest ) )
|
|
|
|
{
|
|
|
|
// N.B. Do not disable the Ratsnest layer here. We use it for local ratsnest
|
|
|
|
Flip( displayOptions().m_ShowGlobalRatsnest );
|
|
|
|
editFrame->SetElementVisibility( LAYER_RATSNEST, displayOptions().m_ShowGlobalRatsnest );
|
2019-06-12 17:32:47 -07:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
}
|
|
|
|
else if( aEvent.IsAction( &PCB_ACTIONS::ratsnestLineMode ) )
|
|
|
|
{
|
|
|
|
Flip( displayOptions().m_DisplayRatsnestLinesCurved );
|
|
|
|
}
|
2019-05-15 23:49:48 +01:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
editFrame->OnDisplayOptionsChanged();
|
2023-01-27 10:01:08 +03:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
canvas()->RedrawRatsnest();
|
|
|
|
canvas()->Refresh();
|
|
|
|
}
|
2018-04-30 09:52:49 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-05-15 23:49:48 +01:00
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::ViaDisplayMode( const TOOL_EVENT& aEvent )
|
2014-03-24 08:45:05 +01:00
|
|
|
{
|
2021-12-26 00:36:12 +00:00
|
|
|
Flip( displayOptions().m_DisplayViaFill );
|
2014-03-24 17:20:23 +01:00
|
|
|
|
2021-09-13 17:18:58 +01:00
|
|
|
for( PCB_TRACK* track : board()->Tracks() )
|
2014-03-24 17:20:23 +01:00
|
|
|
{
|
2022-07-29 22:02:35 +01:00
|
|
|
if( track->Type() == PCB_VIA_T )
|
2021-04-08 17:35:51 -04:00
|
|
|
view()->Update( track, KIGFX::REPAINT );
|
2014-03-24 17:20:23 +01:00
|
|
|
}
|
|
|
|
|
2017-10-30 20:17:23 +01:00
|
|
|
canvas()->Refresh();
|
2014-07-09 15:02:56 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-07-27 15:13:26 +01:00
|
|
|
/**
|
|
|
|
* We have bug reports indicating that some new users confuse zone filling/unfilling with the
|
|
|
|
* display modes. This will put up a warning if they show zone fills when one or more zones
|
|
|
|
* are unfilled.
|
|
|
|
*/
|
|
|
|
void PCB_CONTROL::unfilledZoneCheck()
|
|
|
|
{
|
|
|
|
if( Pgm().GetCommonSettings()->m_DoNotShowAgain.zone_fill_warning )
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool unfilledZones = false;
|
|
|
|
|
|
|
|
for( const ZONE* zone : board()->Zones() )
|
|
|
|
{
|
2022-01-31 16:25:20 +01:00
|
|
|
if( !zone->GetIsRuleArea() && !zone->IsFilled() )
|
2021-07-27 15:13:26 +01:00
|
|
|
{
|
|
|
|
unfilledZones = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( unfilledZones )
|
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
WX_INFOBAR* infobar = m_frame->GetInfoBar();
|
|
|
|
wxHyperlinkCtrl* button = new wxHyperlinkCtrl( infobar, wxID_ANY, _( "Don't show again" ), wxEmptyString );
|
2021-07-27 15:13:26 +01:00
|
|
|
|
|
|
|
button->Bind( wxEVT_COMMAND_HYPERLINK, std::function<void( wxHyperlinkEvent& aEvent )>(
|
|
|
|
[&]( wxHyperlinkEvent& aEvent )
|
|
|
|
{
|
|
|
|
Pgm().GetCommonSettings()->m_DoNotShowAgain.zone_fill_warning = true;
|
2025-07-13 10:47:21 +01:00
|
|
|
m_frame->GetInfoBar()->Dismiss();
|
2021-07-27 15:13:26 +01:00
|
|
|
} ) );
|
|
|
|
|
|
|
|
infobar->RemoveAllButtons();
|
|
|
|
infobar->AddButton( button );
|
|
|
|
|
|
|
|
wxString msg;
|
|
|
|
msg.Printf( _( "Not all zones are filled. Use Edit > Fill All Zones (%s) "
|
2022-02-05 13:25:43 +00:00
|
|
|
"if you wish to see all fills." ),
|
2021-07-27 15:13:26 +01:00
|
|
|
KeyNameFromKeyCode( PCB_ACTIONS::zoneFillAll.GetHotKey() ) );
|
|
|
|
|
2022-01-31 16:25:20 +01:00
|
|
|
infobar->ShowMessageFor( msg, 5000, wxICON_WARNING );
|
2021-07-27 15:13:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::ZoneDisplayMode( const TOOL_EVENT& aEvent )
|
2014-07-09 15:02:56 +02:00
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
PCB_DISPLAY_OPTIONS opts = m_frame->GetDisplayOptions();
|
2014-07-09 15:02:56 +02:00
|
|
|
|
|
|
|
// Apply new display options to the GAL canvas
|
2021-07-26 18:56:11 +01:00
|
|
|
if( aEvent.IsAction( &PCB_ACTIONS::zoneDisplayFilled ) )
|
2020-12-24 00:38:33 +00:00
|
|
|
{
|
2021-07-27 15:13:26 +01:00
|
|
|
unfilledZoneCheck();
|
2021-07-26 22:06:52 +01:00
|
|
|
|
2020-07-11 13:42:00 -04:00
|
|
|
opts.m_ZoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_FILLED;
|
2020-12-24 00:38:33 +00:00
|
|
|
}
|
2021-07-26 18:56:11 +01:00
|
|
|
else if( aEvent.IsAction( &PCB_ACTIONS::zoneDisplayOutline ) )
|
2020-12-24 00:38:33 +00:00
|
|
|
{
|
|
|
|
opts.m_ZoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_ZONE_OUTLINE;
|
|
|
|
}
|
2021-07-26 18:56:11 +01:00
|
|
|
else if( aEvent.IsAction( &PCB_ACTIONS::zoneDisplayFractured ) )
|
2020-12-24 00:38:33 +00:00
|
|
|
{
|
2021-07-26 18:56:11 +01:00
|
|
|
opts.m_ZoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_FRACTURE_BORDERS;
|
|
|
|
}
|
|
|
|
else if( aEvent.IsAction( &PCB_ACTIONS::zoneDisplayTriangulated ) )
|
|
|
|
{
|
|
|
|
opts.m_ZoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_TRIANGULATION;
|
2020-12-24 00:38:33 +00:00
|
|
|
}
|
2019-06-21 11:26:59 +01:00
|
|
|
else if( aEvent.IsAction( &PCB_ACTIONS::zoneDisplayToggle ) )
|
2020-07-11 13:42:00 -04:00
|
|
|
{
|
2021-07-26 18:56:11 +01:00
|
|
|
if( opts.m_ZoneDisplayMode == ZONE_DISPLAY_MODE::SHOW_FILLED )
|
|
|
|
opts.m_ZoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_ZONE_OUTLINE;
|
|
|
|
else
|
|
|
|
opts.m_ZoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_FILLED;
|
2020-07-11 13:42:00 -04:00
|
|
|
}
|
2014-07-09 15:02:56 +02:00
|
|
|
else
|
2020-12-24 00:38:33 +00:00
|
|
|
{
|
2018-06-15 17:11:32 +02:00
|
|
|
wxFAIL;
|
2020-12-24 00:38:33 +00:00
|
|
|
}
|
2014-07-09 15:02:56 +02:00
|
|
|
|
2019-11-07 06:23:09 -08:00
|
|
|
m_frame->SetDisplayOptions( opts );
|
2014-07-09 15:02:56 +02:00
|
|
|
|
2020-11-11 23:05:59 +00:00
|
|
|
for( ZONE* zone : board()->Zones() )
|
2021-04-08 17:35:51 -04:00
|
|
|
view()->Update( zone, KIGFX::REPAINT );
|
2014-07-09 15:02:56 +02:00
|
|
|
|
2017-10-30 20:17:23 +01:00
|
|
|
canvas()->Refresh();
|
2014-03-24 08:45:05 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::HighContrastMode( const TOOL_EVENT& aEvent )
|
2014-03-24 08:45:05 +01:00
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
PCB_DISPLAY_OPTIONS opts = m_frame->GetDisplayOptions();
|
2014-03-24 17:20:23 +01:00
|
|
|
|
2021-10-04 13:42:08 +01:00
|
|
|
opts.m_ContrastModeDisplay = opts.m_ContrastModeDisplay == HIGH_CONTRAST_MODE::NORMAL
|
2025-07-13 10:47:21 +01:00
|
|
|
? HIGH_CONTRAST_MODE::DIMMED
|
|
|
|
: HIGH_CONTRAST_MODE::NORMAL;
|
2020-07-11 13:40:23 -04:00
|
|
|
|
2019-11-07 06:23:09 -08:00
|
|
|
m_frame->SetDisplayOptions( opts );
|
2020-07-11 13:40:23 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::HighContrastModeCycle( const TOOL_EVENT& aEvent )
|
2020-07-11 13:40:23 -04:00
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
PCB_DISPLAY_OPTIONS opts = m_frame->GetDisplayOptions();
|
2020-07-11 13:40:23 -04:00
|
|
|
|
|
|
|
switch( opts.m_ContrastModeDisplay )
|
|
|
|
{
|
2021-09-13 17:18:58 +01:00
|
|
|
case HIGH_CONTRAST_MODE::NORMAL: opts.m_ContrastModeDisplay = HIGH_CONTRAST_MODE::DIMMED; break;
|
|
|
|
case HIGH_CONTRAST_MODE::DIMMED: opts.m_ContrastModeDisplay = HIGH_CONTRAST_MODE::HIDDEN; break;
|
|
|
|
case HIGH_CONTRAST_MODE::HIDDEN: opts.m_ContrastModeDisplay = HIGH_CONTRAST_MODE::NORMAL; break;
|
2020-07-11 13:40:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
m_frame->SetDisplayOptions( opts );
|
|
|
|
|
2023-06-17 23:47:26 -04:00
|
|
|
m_toolMgr->PostEvent( EVENTS::ContrastModeChangedByKeyEvent );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PCB_CONTROL::ContrastModeFeedback( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
if( !Pgm().GetCommonSettings()->m_Input.hotkey_feedback )
|
|
|
|
return 0;
|
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
PCB_DISPLAY_OPTIONS opts = m_frame->GetDisplayOptions();
|
2023-06-17 23:47:26 -04:00
|
|
|
|
|
|
|
wxArrayString labels;
|
|
|
|
labels.Add( _( "Normal" ) );
|
|
|
|
labels.Add( _( "Dimmed" ) );
|
|
|
|
labels.Add( _( "Hidden" ) );
|
|
|
|
|
|
|
|
if( !m_frame->GetHotkeyPopup() )
|
|
|
|
m_frame->CreateHotkeyPopup();
|
|
|
|
|
|
|
|
HOTKEY_CYCLE_POPUP* popup = m_frame->GetHotkeyPopup();
|
|
|
|
|
|
|
|
if( popup )
|
2023-07-15 17:37:17 +01:00
|
|
|
{
|
2023-06-17 23:47:26 -04:00
|
|
|
popup->Popup( _( "Inactive Layer Display" ), labels,
|
|
|
|
static_cast<int>( opts.m_ContrastModeDisplay ) );
|
2023-07-15 17:37:17 +01:00
|
|
|
}
|
2023-06-17 23:47:26 -04:00
|
|
|
|
2014-03-24 08:45:05 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-10-04 13:42:08 +01:00
|
|
|
int PCB_CONTROL::NetColorModeCycle( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
PCB_DISPLAY_OPTIONS opts = m_frame->GetDisplayOptions();
|
2021-10-04 13:42:08 +01:00
|
|
|
|
|
|
|
switch( opts.m_NetColorMode )
|
|
|
|
{
|
|
|
|
case NET_COLOR_MODE::ALL: opts.m_NetColorMode = NET_COLOR_MODE::RATSNEST; break;
|
|
|
|
case NET_COLOR_MODE::RATSNEST: opts.m_NetColorMode = NET_COLOR_MODE::OFF; break;
|
|
|
|
case NET_COLOR_MODE::OFF: opts.m_NetColorMode = NET_COLOR_MODE::ALL; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_frame->SetDisplayOptions( opts );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PCB_CONTROL::RatsnestModeCycle( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
if( PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame ) )
|
2021-10-04 13:42:08 +01:00
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
if( !displayOptions().m_ShowGlobalRatsnest )
|
|
|
|
{
|
|
|
|
displayOptions().m_ShowGlobalRatsnest = true;
|
|
|
|
displayOptions().m_RatsnestMode = RATSNEST_MODE::ALL;
|
|
|
|
}
|
|
|
|
else if( displayOptions().m_RatsnestMode == RATSNEST_MODE::ALL )
|
|
|
|
{
|
|
|
|
displayOptions().m_RatsnestMode = RATSNEST_MODE::VISIBLE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
displayOptions().m_ShowGlobalRatsnest = false;
|
|
|
|
}
|
2021-10-04 13:42:08 +01:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
editFrame->SetElementVisibility( LAYER_RATSNEST, displayOptions().m_ShowGlobalRatsnest );
|
2023-01-27 10:01:08 +03:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
editFrame->OnDisplayOptionsChanged();
|
|
|
|
|
|
|
|
canvas()->RedrawRatsnest();
|
|
|
|
canvas()->Refresh();
|
|
|
|
}
|
2023-01-27 10:01:08 +03:00
|
|
|
|
2021-10-04 13:42:08 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::LayerSwitch( const TOOL_EVENT& aEvent )
|
2014-07-09 16:25:50 +02:00
|
|
|
{
|
2021-12-09 10:14:11 -08:00
|
|
|
m_frame->SwitchLayer( aEvent.Parameter<PCB_LAYER_ID>() );
|
2014-03-24 08:45:05 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::LayerNext( const TOOL_EVENT& aEvent )
|
2014-03-24 08:45:05 +01:00
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
BOARD* brd = board();
|
|
|
|
PCB_LAYER_ID layer = m_frame->GetActiveLayer();
|
|
|
|
bool wraparound = false;
|
2014-06-06 14:59:55 +02:00
|
|
|
|
2024-10-03 18:27:48 +02:00
|
|
|
if( !IsCopperLayer( layer ) )
|
2019-12-29 23:37:25 +00:00
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
m_frame->SwitchLayer( B_Cu );
|
2024-10-03 18:27:48 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
LSET cuMask = LSET::AllCuMask( brd->GetCopperLayerCount() );
|
|
|
|
LSEQ layerStack = cuMask.UIOrder();
|
|
|
|
|
|
|
|
int ii = 0;
|
|
|
|
|
|
|
|
// Find the active layer in list
|
|
|
|
for( ; ii < (int)layerStack.size(); ii++ )
|
|
|
|
{
|
|
|
|
if( layer == layerStack[ii] )
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the next visible layer in list
|
|
|
|
for( ; ii < (int)layerStack.size(); ii++ )
|
|
|
|
{
|
|
|
|
int jj = ii+1;
|
|
|
|
|
|
|
|
if( jj >= (int)layerStack.size() )
|
|
|
|
jj = 0;
|
|
|
|
|
|
|
|
layer = layerStack[jj];
|
|
|
|
|
|
|
|
if( brd->IsLayerVisible( layer ) )
|
2019-12-29 23:37:25 +00:00
|
|
|
break;
|
2014-07-09 16:25:50 +02:00
|
|
|
|
2024-10-03 18:27:48 +02:00
|
|
|
if( jj == 0 ) // the end of list is reached. Try from the beginning
|
2023-11-03 18:17:09 +00:00
|
|
|
{
|
|
|
|
if( wraparound )
|
|
|
|
{
|
|
|
|
wxBell();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
wraparound = true;
|
2024-10-03 18:27:48 +02:00
|
|
|
ii = -1;
|
2023-11-03 18:17:09 +00:00
|
|
|
}
|
|
|
|
}
|
2019-12-29 23:37:25 +00:00
|
|
|
}
|
2014-03-24 08:45:05 +01:00
|
|
|
|
2018-06-15 17:11:32 +02:00
|
|
|
wxCHECK( IsCopperLayer( layer ), 0 );
|
2025-07-13 10:47:21 +01:00
|
|
|
m_frame->SwitchLayer( layer );
|
2014-03-24 08:45:05 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::LayerPrev( const TOOL_EVENT& aEvent )
|
2014-03-24 08:45:05 +01:00
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
BOARD* brd = board();
|
|
|
|
PCB_LAYER_ID layer = m_frame->GetActiveLayer();
|
|
|
|
bool wraparound = false;
|
2014-03-24 08:45:05 +01:00
|
|
|
|
2024-10-03 18:27:48 +02:00
|
|
|
if( !IsCopperLayer( layer ) )
|
2019-12-29 23:37:25 +00:00
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
m_frame->SwitchLayer( F_Cu );
|
2024-10-03 18:27:48 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
LSET cuMask = LSET::AllCuMask( brd->GetCopperLayerCount() );
|
|
|
|
LSEQ layerStack = cuMask.UIOrder();
|
|
|
|
|
|
|
|
int ii = 0;
|
|
|
|
|
|
|
|
// Find the active layer in list
|
|
|
|
for( ; ii < (int)layerStack.size(); ii++ )
|
|
|
|
{
|
|
|
|
if( layer == layerStack[ii] )
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the previous visible layer in list
|
|
|
|
for( ; ii >= 0; ii-- )
|
|
|
|
{
|
|
|
|
int jj = ii - 1;
|
|
|
|
|
|
|
|
if( jj < 0 )
|
|
|
|
jj = (int)layerStack.size() - 1;
|
|
|
|
|
|
|
|
layer = layerStack[jj];
|
|
|
|
|
|
|
|
if( brd->IsLayerVisible( layer ) )
|
2019-12-29 23:37:25 +00:00
|
|
|
break;
|
|
|
|
|
2024-10-03 18:27:48 +02:00
|
|
|
if( ii == 0 ) // the start of list is reached. Try from the last
|
2023-11-03 18:17:09 +00:00
|
|
|
{
|
|
|
|
if( wraparound )
|
|
|
|
{
|
|
|
|
wxBell();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
wraparound = true;
|
2024-10-03 18:27:48 +02:00
|
|
|
ii = 1;
|
2023-11-03 18:17:09 +00:00
|
|
|
}
|
|
|
|
}
|
2019-12-29 23:37:25 +00:00
|
|
|
}
|
2014-07-09 16:25:50 +02:00
|
|
|
|
2018-06-15 17:11:32 +02:00
|
|
|
wxCHECK( IsCopperLayer( layer ), 0 );
|
2025-07-13 10:47:21 +01:00
|
|
|
m_frame->SwitchLayer( layer );
|
2014-03-24 08:45:05 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::LayerToggle( const TOOL_EVENT& aEvent )
|
2015-05-18 13:48:11 +02:00
|
|
|
{
|
2021-07-22 00:14:56 +01:00
|
|
|
int currentLayer = m_frame->GetActiveLayer();
|
2015-05-18 13:48:11 +02:00
|
|
|
PCB_SCREEN* screen = m_frame->GetScreen();
|
|
|
|
|
|
|
|
if( currentLayer == screen->m_Route_Layer_TOP )
|
2021-12-09 10:14:11 -08:00
|
|
|
m_frame->SwitchLayer( screen->m_Route_Layer_BOTTOM );
|
2015-05-18 13:48:11 +02:00
|
|
|
else
|
2021-12-09 10:14:11 -08:00
|
|
|
m_frame->SwitchLayer( screen->m_Route_Layer_TOP );
|
2015-05-18 13:48:11 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-09-22 12:54:01 +01:00
|
|
|
// It'd be nice to share the min/max with the DIALOG_COLOR_PICKER, but those are
|
2018-02-23 13:04:07 +00:00
|
|
|
// set in wxFormBuilder.
|
|
|
|
#define ALPHA_MIN 0.20
|
|
|
|
#define ALPHA_MAX 1.00
|
|
|
|
#define ALPHA_STEP 0.05
|
|
|
|
|
2021-07-19 19:56:05 -04:00
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::LayerAlphaInc( const TOOL_EVENT& aEvent )
|
2014-03-24 08:45:05 +01:00
|
|
|
{
|
2021-09-13 17:18:58 +01:00
|
|
|
COLOR_SETTINGS* settings = m_frame->GetColorSettings();
|
2021-07-22 00:14:56 +01:00
|
|
|
int currentLayer = m_frame->GetActiveLayer();
|
2021-09-13 17:18:58 +01:00
|
|
|
KIGFX::COLOR4D currentColor = settings->GetColor( currentLayer );
|
2014-03-24 18:28:21 +01:00
|
|
|
|
2018-02-23 13:04:07 +00:00
|
|
|
if( currentColor.a <= ALPHA_MAX - ALPHA_STEP )
|
2014-03-24 18:28:21 +01:00
|
|
|
{
|
2018-02-23 13:04:07 +00:00
|
|
|
currentColor.a += ALPHA_STEP;
|
2020-01-12 20:44:19 -05:00
|
|
|
settings->SetColor( currentLayer, currentColor );
|
|
|
|
m_frame->GetCanvas()->UpdateColors();
|
2019-05-26 20:20:18 -04:00
|
|
|
|
2019-06-13 18:28:55 +01:00
|
|
|
KIGFX::VIEW* view = m_frame->GetCanvas()->GetView();
|
2019-05-26 20:20:18 -04:00
|
|
|
view->UpdateLayerColor( currentLayer );
|
2021-02-09 12:26:08 +00:00
|
|
|
view->UpdateLayerColor( GetNetnameLayer( currentLayer ) );
|
|
|
|
|
|
|
|
if( IsCopperLayer( currentLayer ) )
|
|
|
|
view->UpdateLayerColor( ZONE_LAYER_FOR( currentLayer ) );
|
2025-01-29 11:23:40 +00:00
|
|
|
|
|
|
|
m_frame->GetCanvas()->ForceRefresh();
|
2014-03-24 18:28:21 +01:00
|
|
|
}
|
2018-02-23 13:04:07 +00:00
|
|
|
else
|
2021-09-13 17:18:58 +01:00
|
|
|
{
|
2018-02-23 13:04:07 +00:00
|
|
|
wxBell();
|
2021-09-13 17:18:58 +01:00
|
|
|
}
|
2014-03-24 18:28:21 +01:00
|
|
|
|
2014-03-24 08:45:05 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::LayerAlphaDec( const TOOL_EVENT& aEvent )
|
2014-03-24 08:45:05 +01:00
|
|
|
{
|
2021-09-13 17:18:58 +01:00
|
|
|
COLOR_SETTINGS* settings = m_frame->GetColorSettings();
|
2021-07-22 00:14:56 +01:00
|
|
|
int currentLayer = m_frame->GetActiveLayer();
|
2021-09-13 17:18:58 +01:00
|
|
|
KIGFX::COLOR4D currentColor = settings->GetColor( currentLayer );
|
2014-03-24 18:28:21 +01:00
|
|
|
|
2018-02-23 13:04:07 +00:00
|
|
|
if( currentColor.a >= ALPHA_MIN + ALPHA_STEP )
|
2014-03-24 18:28:21 +01:00
|
|
|
{
|
2018-02-23 13:04:07 +00:00
|
|
|
currentColor.a -= ALPHA_STEP;
|
2020-01-12 20:44:19 -05:00
|
|
|
settings->SetColor( currentLayer, currentColor );
|
|
|
|
m_frame->GetCanvas()->UpdateColors();
|
2019-05-26 20:20:18 -04:00
|
|
|
|
2019-06-13 18:28:55 +01:00
|
|
|
KIGFX::VIEW* view = m_frame->GetCanvas()->GetView();
|
2019-05-26 20:20:18 -04:00
|
|
|
view->UpdateLayerColor( currentLayer );
|
2021-02-09 12:26:08 +00:00
|
|
|
view->UpdateLayerColor( GetNetnameLayer( currentLayer ) );
|
|
|
|
|
|
|
|
if( IsCopperLayer( currentLayer ) )
|
|
|
|
view->UpdateLayerColor( ZONE_LAYER_FOR( currentLayer ) );
|
2025-01-29 11:23:40 +00:00
|
|
|
|
|
|
|
m_frame->GetCanvas()->ForceRefresh();
|
2014-03-24 18:28:21 +01:00
|
|
|
}
|
2018-02-23 13:04:07 +00:00
|
|
|
else
|
2021-07-19 19:56:05 -04:00
|
|
|
{
|
2018-02-23 13:04:07 +00:00
|
|
|
wxBell();
|
2021-07-19 19:56:05 -04:00
|
|
|
}
|
2014-03-24 18:28:21 +01:00
|
|
|
|
2014-03-24 08:45:05 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-07-31 19:31:07 +08:00
|
|
|
int PCB_CONTROL::CycleLayerPresets( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
2025-07-13 10:47:21 +01:00
|
|
|
if( PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame ) )
|
|
|
|
{
|
|
|
|
LAYER_PAIR_SETTINGS* settings = editFrame->GetLayerPairSettings();
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
if( !settings )
|
|
|
|
return 0;
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
int currentIndex;
|
|
|
|
std::vector<LAYER_PAIR_INFO> presets = settings->GetEnabledLayerPairs( currentIndex );
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
if( presets.size() < 2 )
|
|
|
|
return 0;
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
if( currentIndex < 0 )
|
|
|
|
{
|
|
|
|
wxASSERT_MSG( false, "Current layer pair not found in layer settings" );
|
|
|
|
currentIndex = 0;
|
|
|
|
}
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
const int nextIndex = ( currentIndex + 1 ) % presets.size();
|
|
|
|
const LAYER_PAIR& nextPair = presets[nextIndex].GetLayerPair();
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
settings->SetCurrentLayerPair( nextPair );
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
m_toolMgr->PostEvent( PCB_EVENTS::LayerPairPresetChangedByKeyEvent() );
|
|
|
|
}
|
2024-07-31 19:31:07 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PCB_CONTROL::LayerPresetFeedback( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
if( !Pgm().GetCommonSettings()->m_Input.hotkey_feedback )
|
|
|
|
return 0;
|
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
if( PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame ) )
|
|
|
|
{
|
|
|
|
LAYER_PAIR_SETTINGS* settings = editFrame->GetLayerPairSettings();
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
if( !settings )
|
|
|
|
return 0;
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
PCB_LAYER_PRESENTATION layerPresentation( editFrame );
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
int currentIndex;
|
|
|
|
std::vector<LAYER_PAIR_INFO> presets = settings->GetEnabledLayerPairs( currentIndex );
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
wxArrayString labels;
|
|
|
|
for( const LAYER_PAIR_INFO& layerPairInfo : presets )
|
|
|
|
{
|
|
|
|
wxString label = layerPresentation.getLayerPairName( layerPairInfo.GetLayerPair() );
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
if( layerPairInfo.GetName() )
|
|
|
|
label += wxT( " (" ) + *layerPairInfo.GetName() + wxT( ")" );
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
labels.Add( label );
|
|
|
|
}
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
if( !editFrame->GetHotkeyPopup() )
|
|
|
|
editFrame->CreateHotkeyPopup();
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
HOTKEY_CYCLE_POPUP* popup = editFrame->GetHotkeyPopup();
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
if( popup )
|
|
|
|
{
|
|
|
|
int selection = currentIndex;
|
|
|
|
popup->Popup( _( "Preset Layer Pairs" ), labels, selection );
|
|
|
|
}
|
2024-07-31 19:31:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
void PCB_CONTROL::DoSetGridOrigin( KIGFX::VIEW* aView, PCB_BASE_FRAME* aFrame,
|
|
|
|
EDA_ITEM* originViewItem, const VECTOR2D& aPoint )
|
2014-06-04 17:46:43 +02:00
|
|
|
{
|
2023-02-18 22:40:07 -05:00
|
|
|
aFrame->GetDesignSettings().SetGridOrigin( VECTOR2I( aPoint ) );
|
2015-06-18 17:51:51 +02:00
|
|
|
aView->GetGAL()->SetGridOrigin( aPoint );
|
2022-08-30 15:13:51 +01:00
|
|
|
originViewItem->SetPosition( aPoint );
|
2015-06-18 17:51:51 +02:00
|
|
|
aView->MarkDirty();
|
2018-01-24 21:27:56 +00:00
|
|
|
aFrame->OnModify();
|
2015-06-18 17:51:51 +02:00
|
|
|
}
|
2014-06-04 17:46:43 +02:00
|
|
|
|
|
|
|
|
2023-08-26 13:29:24 +01:00
|
|
|
int PCB_CONTROL::GridPlaceOrigin( const TOOL_EVENT& aEvent )
|
2015-06-18 17:51:51 +02:00
|
|
|
{
|
2015-06-30 14:08:03 +02:00
|
|
|
VECTOR2D* origin = aEvent.Parameter<VECTOR2D*>();
|
2015-06-18 17:51:51 +02:00
|
|
|
|
2015-06-30 14:08:03 +02:00
|
|
|
if( origin )
|
|
|
|
{
|
2018-01-24 10:19:33 +00:00
|
|
|
// We can't undo the other grid dialog settings, so no sense undoing just the origin
|
|
|
|
DoSetGridOrigin( getView(), m_frame, m_gridOrigin.get(), *origin );
|
2015-06-30 14:08:03 +02:00
|
|
|
delete origin;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-11-07 17:50:22 +00:00
|
|
|
if( m_isFootprintEditor && !getEditFrame<PCB_BASE_EDIT_FRAME>()->GetModel() )
|
2019-07-19 15:13:52 -06:00
|
|
|
return 0;
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
|
2015-06-30 14:08:03 +02:00
|
|
|
|
2021-05-07 10:35:00 +02:00
|
|
|
if( !picker ) // Happens in footprint wizard
|
|
|
|
return 0;
|
|
|
|
|
2019-07-19 14:47:33 -06:00
|
|
|
// Deactivate other tools; particularly important if another PICKER is currently running
|
|
|
|
Activate();
|
|
|
|
|
2025-07-04 12:10:59 -06:00
|
|
|
picker->SetCursor( KICURSOR::PLACE );
|
|
|
|
picker->ClearHandlers();
|
|
|
|
|
2019-07-16 00:44:01 +01:00
|
|
|
picker->SetClickHandler(
|
2021-09-13 17:18:58 +01:00
|
|
|
[this]( const VECTOR2D& pt ) -> bool
|
|
|
|
{
|
|
|
|
m_frame->SaveCopyInUndoList( m_gridOrigin.get(), UNDO_REDO::GRIDORIGIN );
|
|
|
|
DoSetGridOrigin( getView(), m_frame, m_gridOrigin.get(), pt );
|
|
|
|
return false; // drill origin is a one-shot; don't continue with tool
|
|
|
|
} );
|
2019-06-24 16:27:05 +01:00
|
|
|
|
2023-06-26 23:16:51 +01:00
|
|
|
m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent );
|
2015-06-30 14:08:03 +02:00
|
|
|
}
|
2014-06-04 17:46:43 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::GridResetOrigin( const TOOL_EVENT& aEvent )
|
2016-05-04 14:59:14 +02:00
|
|
|
{
|
2020-08-26 18:04:32 +00:00
|
|
|
m_frame->SaveCopyInUndoList( m_gridOrigin.get(), UNDO_REDO::GRIDORIGIN );
|
2019-06-24 16:27:05 +01:00
|
|
|
DoSetGridOrigin( getView(), m_frame, m_gridOrigin.get(), VECTOR2D( 0, 0 ) );
|
2016-05-04 14:59:14 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-25 14:01:22 +01:00
|
|
|
#define HITTEST_THRESHOLD_PIXELS 5
|
|
|
|
|
|
|
|
|
2023-10-10 15:43:45 +01:00
|
|
|
int PCB_CONTROL::InteractiveDelete( const TOOL_EVENT& aEvent )
|
2015-06-18 17:51:51 +02:00
|
|
|
{
|
2020-11-08 21:29:04 +00:00
|
|
|
if( m_isFootprintEditor && !m_frame->GetBoard()->GetFirstFootprint() )
|
2019-07-19 15:13:52 -06:00
|
|
|
return 0;
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
|
2019-07-16 00:44:01 +01:00
|
|
|
|
2019-06-25 14:01:22 +01:00
|
|
|
m_pickerItem = nullptr;
|
2025-04-02 11:01:22 -04:00
|
|
|
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
2015-06-18 17:51:51 +02:00
|
|
|
|
2019-07-19 14:47:33 -06:00
|
|
|
// Deactivate other tools; particularly important if another PICKER is currently running
|
|
|
|
Activate();
|
|
|
|
|
2020-10-08 07:39:51 -04:00
|
|
|
picker->SetCursor( KICURSOR::REMOVE );
|
2025-07-04 12:10:59 -06:00
|
|
|
picker->SetSnapping( false );
|
|
|
|
picker->ClearHandlers();
|
2019-07-16 00:44:01 +01:00
|
|
|
|
|
|
|
picker->SetClickHandler(
|
2021-09-13 17:18:58 +01:00
|
|
|
[this]( const VECTOR2D& aPosition ) -> bool
|
2019-06-25 14:01:22 +01:00
|
|
|
{
|
2021-09-13 17:18:58 +01:00
|
|
|
if( m_pickerItem )
|
2019-07-16 00:44:01 +01:00
|
|
|
{
|
2021-09-13 17:18:58 +01:00
|
|
|
if( m_pickerItem && m_pickerItem->IsLocked() )
|
|
|
|
{
|
|
|
|
m_statusPopup.reset( new STATUS_TEXT_POPUP( m_frame ) );
|
|
|
|
m_statusPopup->SetText( _( "Item locked." ) );
|
|
|
|
m_statusPopup->PopupFor( 2000 );
|
2025-04-12 20:49:19 +01:00
|
|
|
m_statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) );
|
2021-09-13 17:18:58 +01:00
|
|
|
return true;
|
|
|
|
}
|
2019-07-16 00:44:01 +01:00
|
|
|
|
2021-09-13 17:18:58 +01:00
|
|
|
PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
selectionTool->UnbrightenItem( m_pickerItem );
|
2022-10-17 13:02:39 +01:00
|
|
|
|
|
|
|
PCB_SELECTION items;
|
|
|
|
items.Add( m_pickerItem );
|
|
|
|
|
|
|
|
EDIT_TOOL* editTool = m_toolMgr->GetTool<EDIT_TOOL>();
|
|
|
|
editTool->DeleteItems( items, false );
|
|
|
|
|
2021-09-13 17:18:58 +01:00
|
|
|
m_pickerItem = nullptr;
|
|
|
|
}
|
2019-06-25 14:01:22 +01:00
|
|
|
|
2021-09-13 17:18:58 +01:00
|
|
|
return true;
|
|
|
|
} );
|
2019-06-24 16:27:05 +01:00
|
|
|
|
2019-07-16 00:44:01 +01:00
|
|
|
picker->SetMotionHandler(
|
2021-09-13 17:18:58 +01:00
|
|
|
[this]( const VECTOR2D& aPos )
|
2019-07-28 12:30:56 -06:00
|
|
|
{
|
2021-09-13 17:18:58 +01:00
|
|
|
BOARD* board = m_frame->GetBoard();
|
|
|
|
PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
GENERAL_COLLECTORS_GUIDE guide = m_frame->GetCollectorsGuide();
|
|
|
|
GENERAL_COLLECTOR collector;
|
|
|
|
collector.m_Threshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) );
|
|
|
|
|
|
|
|
if( m_isFootprintEditor )
|
2022-07-29 22:02:35 +01:00
|
|
|
collector.Collect( board, GENERAL_COLLECTOR::FootprintItems, aPos, guide );
|
2021-09-13 17:18:58 +01:00
|
|
|
else
|
2022-07-29 22:02:35 +01:00
|
|
|
collector.Collect( board, GENERAL_COLLECTOR::BoardLevelItems, aPos, guide );
|
2021-09-13 17:18:58 +01:00
|
|
|
|
|
|
|
// Remove unselectable items
|
|
|
|
for( int i = collector.GetCount() - 1; i >= 0; --i )
|
|
|
|
{
|
|
|
|
if( !selectionTool->Selectable( collector[ i ] ) )
|
|
|
|
collector.Remove( i );
|
|
|
|
}
|
2019-07-28 12:30:56 -06:00
|
|
|
|
2023-10-10 16:04:54 +01:00
|
|
|
selectionTool->FilterCollectorForHierarchy( collector, false );
|
2025-08-29 07:06:41 -07:00
|
|
|
selectionTool->FilterCollectedItems( collector, false, nullptr );
|
2023-10-10 16:04:54 +01:00
|
|
|
|
2021-09-13 17:18:58 +01:00
|
|
|
if( collector.GetCount() > 1 )
|
|
|
|
selectionTool->GuessSelectionCandidates( collector, aPos );
|
2019-07-28 12:30:56 -06:00
|
|
|
|
2021-09-13 17:18:58 +01:00
|
|
|
BOARD_ITEM* item = collector.GetCount() == 1 ? collector[ 0 ] : nullptr;
|
2019-06-25 14:01:22 +01:00
|
|
|
|
2021-09-13 17:18:58 +01:00
|
|
|
if( m_pickerItem != item )
|
|
|
|
{
|
|
|
|
if( m_pickerItem )
|
|
|
|
selectionTool->UnbrightenItem( m_pickerItem );
|
2019-06-25 14:01:22 +01:00
|
|
|
|
2021-09-13 17:18:58 +01:00
|
|
|
m_pickerItem = item;
|
2019-06-25 14:01:22 +01:00
|
|
|
|
2021-09-13 17:18:58 +01:00
|
|
|
if( m_pickerItem )
|
|
|
|
selectionTool->BrightenItem( m_pickerItem );
|
|
|
|
}
|
|
|
|
} );
|
2019-06-25 14:01:22 +01:00
|
|
|
|
2019-07-16 00:44:01 +01:00
|
|
|
picker->SetFinalizeHandler(
|
2021-09-13 17:18:58 +01:00
|
|
|
[this]( const int& aFinalState )
|
|
|
|
{
|
|
|
|
if( m_pickerItem )
|
|
|
|
m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->UnbrightenItem( m_pickerItem );
|
2020-10-11 13:12:13 -04:00
|
|
|
|
2021-09-13 17:18:58 +01:00
|
|
|
m_statusPopup.reset();
|
2021-05-26 02:18:34 +02:00
|
|
|
|
2021-09-13 17:18:58 +01:00
|
|
|
// Ensure the cursor gets changed&updated
|
|
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
|
|
|
|
m_frame->GetCanvas()->Refresh();
|
|
|
|
} );
|
2019-06-25 14:01:22 +01:00
|
|
|
|
2023-06-26 23:16:51 +01:00
|
|
|
m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent );
|
2019-07-15 13:15:58 +01:00
|
|
|
|
2015-06-18 17:51:51 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2017-05-18 19:21:30 +02:00
|
|
|
|
2018-05-08 10:36:50 +02:00
|
|
|
|
2020-11-25 17:09:23 +01:00
|
|
|
static void pasteFootprintItemsToFootprintEditor( FOOTPRINT* aClipFootprint, BOARD* aBoard,
|
|
|
|
std::vector<BOARD_ITEM*>& aPastedItems )
|
2020-03-16 15:47:01 +00:00
|
|
|
{
|
2020-11-13 15:15:52 +00:00
|
|
|
FOOTPRINT* editorFootprint = aBoard->GetFirstFootprint();
|
2020-03-16 15:47:01 +00:00
|
|
|
|
2020-11-08 21:29:04 +00:00
|
|
|
aClipFootprint->SetParent( aBoard );
|
2020-03-16 15:47:01 +00:00
|
|
|
|
2020-11-12 22:30:02 +00:00
|
|
|
for( PAD* pad : aClipFootprint->Pads() )
|
2020-03-16 15:47:01 +00:00
|
|
|
{
|
2020-11-08 21:29:04 +00:00
|
|
|
pad->SetParent( editorFootprint );
|
2020-03-16 15:47:01 +00:00
|
|
|
aPastedItems.push_back( pad );
|
|
|
|
}
|
|
|
|
|
2020-11-08 21:29:04 +00:00
|
|
|
aClipFootprint->Pads().clear();
|
2020-03-16 15:47:01 +00:00
|
|
|
|
2024-05-03 21:30:46 +01:00
|
|
|
// Not all items can be added to the current footprint: mandatory fields are already existing
|
|
|
|
// in the current footprint.
|
2024-02-16 10:21:44 +00:00
|
|
|
//
|
2024-12-28 20:40:57 +00:00
|
|
|
for( PCB_FIELD* field : aClipFootprint->GetFields() )
|
2024-02-16 10:21:44 +00:00
|
|
|
{
|
2025-01-22 14:26:57 +00:00
|
|
|
if( field->IsMandatory() )
|
2024-02-16 10:21:44 +00:00
|
|
|
{
|
2025-04-01 14:20:03 -04:00
|
|
|
if( EDA_GROUP* parentGroup = field->GetParentGroup() )
|
2024-02-16 10:21:44 +00:00
|
|
|
parentGroup->RemoveItem( field );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
PCB_TEXT* text = static_cast<PCB_TEXT*>( field );
|
|
|
|
|
|
|
|
text->SetTextAngle( text->GetTextAngle() - aClipFootprint->GetOrientation() );
|
|
|
|
text->SetTextAngle( text->GetTextAngle() + editorFootprint->GetOrientation() );
|
|
|
|
|
|
|
|
VECTOR2I pos = field->GetFPRelativePosition();
|
|
|
|
field->SetParent( editorFootprint );
|
|
|
|
field->SetFPRelativePosition( pos );
|
|
|
|
|
|
|
|
aPastedItems.push_back( field );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-08 13:45:57 +00:00
|
|
|
aClipFootprint->GetFields().clear();
|
2024-02-16 10:21:44 +00:00
|
|
|
|
2020-11-08 21:29:04 +00:00
|
|
|
for( BOARD_ITEM* item : aClipFootprint->GraphicalItems() )
|
2020-03-16 15:47:01 +00:00
|
|
|
{
|
2023-03-30 12:49:23 +01:00
|
|
|
if( item->Type() == PCB_TEXT_T )
|
2020-03-16 15:47:01 +00:00
|
|
|
{
|
2023-03-30 12:49:23 +01:00
|
|
|
PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
|
2020-03-16 15:47:01 +00:00
|
|
|
|
2023-03-30 12:49:23 +01:00
|
|
|
text->SetTextAngle( text->GetTextAngle() - aClipFootprint->GetOrientation() );
|
|
|
|
text->SetTextAngle( text->GetTextAngle() + editorFootprint->GetOrientation() );
|
2020-03-16 15:47:01 +00:00
|
|
|
}
|
|
|
|
|
2024-05-03 21:30:46 +01:00
|
|
|
item->Rotate( item->GetPosition(), -aClipFootprint->GetOrientation() );
|
|
|
|
item->Rotate( item->GetPosition(), editorFootprint->GetOrientation() );
|
|
|
|
|
2023-03-30 12:49:23 +01:00
|
|
|
VECTOR2I pos = item->GetFPRelativePosition();
|
2020-11-08 21:29:04 +00:00
|
|
|
item->SetParent( editorFootprint );
|
2023-03-30 12:49:23 +01:00
|
|
|
item->SetFPRelativePosition( pos );
|
|
|
|
|
2020-03-16 15:47:01 +00:00
|
|
|
aPastedItems.push_back( item );
|
|
|
|
}
|
|
|
|
|
2020-11-08 21:29:04 +00:00
|
|
|
aClipFootprint->GraphicalItems().clear();
|
2020-03-16 15:47:01 +00:00
|
|
|
|
2023-03-30 12:49:23 +01:00
|
|
|
for( ZONE* zone : aClipFootprint->Zones() )
|
2021-02-07 11:11:00 +00:00
|
|
|
{
|
|
|
|
zone->SetParent( editorFootprint );
|
|
|
|
aPastedItems.push_back( zone );
|
|
|
|
}
|
|
|
|
|
|
|
|
aClipFootprint->Zones().clear();
|
|
|
|
|
2020-11-08 21:29:04 +00:00
|
|
|
for( PCB_GROUP* group : aClipFootprint->Groups() )
|
2020-10-20 13:37:36 +01:00
|
|
|
{
|
2020-11-08 21:29:04 +00:00
|
|
|
group->SetParent( editorFootprint );
|
2020-10-20 13:37:36 +01:00
|
|
|
aPastedItems.push_back( group );
|
|
|
|
}
|
|
|
|
|
2020-11-08 21:29:04 +00:00
|
|
|
aClipFootprint->Groups().clear();
|
2020-03-16 15:47:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-09-09 21:01:34 +01:00
|
|
|
void PCB_CONTROL::pruneItemLayers( std::vector<BOARD_ITEM*>& aItems )
|
|
|
|
{
|
2023-01-01 13:35:05 +01:00
|
|
|
// Do not prune items or layers when copying to the FP editor, because all
|
|
|
|
// layers are accepted, even if they are not enabled in the dummy board
|
|
|
|
// This is mainly true for internal copper layers: all are allowed but only one
|
|
|
|
// (In1.cu) is enabled for the GUI.
|
2025-07-13 10:47:21 +01:00
|
|
|
if( m_isFootprintEditor || m_frame->IsType( FRAME_FOOTPRINT_EDITOR ) )
|
2023-01-01 13:35:05 +01:00
|
|
|
return;
|
|
|
|
|
2022-09-09 21:01:34 +01:00
|
|
|
LSET enabledLayers = board()->GetEnabledLayers();
|
|
|
|
std::vector<BOARD_ITEM*> returnItems;
|
|
|
|
bool fpItemDeleted = false;
|
|
|
|
|
|
|
|
for( BOARD_ITEM* item : aItems )
|
|
|
|
{
|
|
|
|
if( item->Type() == PCB_FOOTPRINT_T )
|
|
|
|
{
|
|
|
|
FOOTPRINT* fp = static_cast<FOOTPRINT*>( item );
|
|
|
|
|
2024-07-28 19:58:28 +02:00
|
|
|
// Items living in a parent footprint are never removed, even if their
|
|
|
|
// layer does not exist in the board editor
|
|
|
|
// Otherwise the parent footprint could be seriously broken especially
|
|
|
|
// if some layers are later re-enabled.
|
|
|
|
// Moreover a fp lives in a fp library, that does not know the enabled
|
|
|
|
// layers of a given board, so fp items are just ignored when on not
|
|
|
|
// enabled layers in board editor
|
|
|
|
returnItems.push_back( fp );
|
2022-09-09 21:01:34 +01:00
|
|
|
}
|
2023-10-06 20:04:00 +03:00
|
|
|
else if( item->Type() == PCB_GROUP_T || item->Type() == PCB_GENERATOR_T )
|
2022-11-20 22:51:28 +00:00
|
|
|
{
|
|
|
|
returnItems.push_back( item );
|
|
|
|
}
|
2022-09-09 21:01:34 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
LSET allowed = item->GetLayerSet() & enabledLayers;
|
2024-09-23 17:34:59 +02:00
|
|
|
bool item_valid = true;
|
2022-09-09 21:01:34 +01:00
|
|
|
|
2024-09-23 17:34:59 +02:00
|
|
|
// Ensure, for vias, the top and bottom layers are compatible with
|
|
|
|
// the current board copper layers.
|
|
|
|
// Otherwise they must be skipped, even is one layer is valid
|
|
|
|
if( item->Type() == PCB_VIA_T )
|
|
|
|
item_valid = static_cast<PCB_VIA*>( item )->HasValidLayerPair(
|
|
|
|
board()->GetCopperLayerCount() );
|
|
|
|
|
|
|
|
if( allowed.any() && item_valid )
|
2022-09-09 21:01:34 +01:00
|
|
|
{
|
|
|
|
item->SetLayerSet( allowed );
|
|
|
|
returnItems.push_back( item );
|
|
|
|
}
|
2024-02-16 10:21:44 +00:00
|
|
|
else
|
|
|
|
{
|
2025-04-01 14:20:03 -04:00
|
|
|
if( EDA_GROUP* parentGroup = item->GetParentGroup() )
|
2024-02-16 10:21:44 +00:00
|
|
|
parentGroup->RemoveItem( item );
|
|
|
|
}
|
2022-09-09 21:01:34 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( ( returnItems.size() < aItems.size() ) || fpItemDeleted )
|
|
|
|
{
|
|
|
|
DisplayError( m_frame, _( "Warning: some pasted items were on layers which are not "
|
|
|
|
"present in the current board.\n"
|
|
|
|
"These items could not be pasted.\n" ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
aItems = returnItems;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::Paste( const TOOL_EVENT& aEvent )
|
2017-04-15 18:08:23 +02:00
|
|
|
{
|
2024-10-13 14:57:53 +08:00
|
|
|
// The viewer frames cannot paste
|
2025-07-13 10:47:21 +01:00
|
|
|
if( !m_frame->IsType( FRAME_FOOTPRINT_EDITOR ) && !m_frame->IsType( FRAME_PCB_EDITOR ) )
|
2024-10-13 14:57:53 +08:00
|
|
|
return 0;
|
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
bool isFootprintEditor = m_isFootprintEditor || m_frame->IsType( FRAME_FOOTPRINT_EDITOR );
|
|
|
|
|
2024-10-13 14:57:53 +08:00
|
|
|
// The clipboard can contain two different things, an entire kicad_pcb or a single footprint
|
|
|
|
if( isFootprintEditor && ( !board() || !footprint() ) )
|
|
|
|
return 0;
|
|
|
|
|
2024-10-17 10:03:05 +01:00
|
|
|
// We should never get here if a modal dialog is up... but we do on MacOS.
|
2024-10-15 16:57:09 +01:00
|
|
|
// https://gitlab.com/kicad/code/kicad/-/issues/18912
|
|
|
|
#ifdef __WXMAC__
|
|
|
|
if( wxDialog::OSXHasModalDialogsOpen() )
|
|
|
|
{
|
|
|
|
wxBell();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
BOARD_COMMIT commit( m_frame );
|
2024-10-13 14:57:53 +08:00
|
|
|
|
2017-04-15 18:08:23 +02:00
|
|
|
CLIPBOARD_IO pi;
|
2023-06-27 16:52:50 +01:00
|
|
|
BOARD_ITEM* clipItem = pi.Parse();
|
2017-09-22 17:17:38 +02:00
|
|
|
|
2017-09-29 11:40:10 +02:00
|
|
|
if( !clipItem )
|
2024-10-13 14:57:53 +08:00
|
|
|
{
|
2024-10-13 16:32:20 +08:00
|
|
|
// When the clipboard doesn't parse, create a PCB item with the clipboard contents
|
|
|
|
std::vector<BOARD_ITEM*> newItems;
|
2017-09-22 17:17:38 +02:00
|
|
|
|
2024-10-13 16:32:20 +08:00
|
|
|
if( std::unique_ptr<wxImage> clipImg = GetImageFromClipboard() )
|
|
|
|
{
|
|
|
|
auto refImg = std::make_unique<PCB_REFERENCE_IMAGE>( m_frame->GetModel() );
|
2024-10-13 14:57:53 +08:00
|
|
|
|
2024-10-13 16:32:20 +08:00
|
|
|
if( refImg->GetReferenceImage().SetImage( *clipImg ) )
|
|
|
|
newItems.push_back( refImg.release() );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const wxString clipText = GetClipboardUTF8();
|
2024-10-13 14:57:53 +08:00
|
|
|
|
2024-10-13 16:32:20 +08:00
|
|
|
if( clipText.empty() )
|
|
|
|
return 0;
|
|
|
|
|
2025-04-30 13:25:56 -07:00
|
|
|
// If it wasn't content, then paste as a text object.
|
|
|
|
if( clipText.size() > static_cast<size_t>( ADVANCED_CFG::GetCfg().m_MaxPastedTextLength ) )
|
|
|
|
{
|
|
|
|
int result = IsOK( m_frame, _( "Pasting a long text text string may be very slow. "
|
|
|
|
"Do you want to continue?" ) );
|
|
|
|
if( !result )
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2025-04-12 20:49:19 +01:00
|
|
|
std::unique_ptr<PCB_TEXT> item = std::make_unique<PCB_TEXT>( m_frame->GetModel() );
|
2024-10-13 16:32:20 +08:00
|
|
|
item->SetText( clipText );
|
|
|
|
|
|
|
|
newItems.push_back( item.release() );
|
|
|
|
}
|
2024-10-13 14:57:53 +08:00
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
bool cancelled = !placeBoardItems( &commit, newItems, true, false, false, false );
|
2024-10-13 14:57:53 +08:00
|
|
|
|
|
|
|
if( cancelled )
|
|
|
|
commit.Revert();
|
|
|
|
else
|
|
|
|
commit.Push( _( "Paste Text" ) );
|
2020-10-30 16:33:24 -07:00
|
|
|
return 0;
|
2024-10-13 14:57:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// If we get here, we have a parsed board/FP to paste
|
2020-10-30 16:33:24 -07:00
|
|
|
|
2022-07-29 22:02:35 +01:00
|
|
|
PASTE_MODE mode = PASTE_MODE::KEEP_ANNOTATIONS;
|
2024-04-01 16:55:37 -07:00
|
|
|
bool clear_nets = false;
|
2021-05-01 23:44:22 +01:00
|
|
|
const wxString defaultRef = wxT( "REF**" );
|
|
|
|
|
|
|
|
if( aEvent.IsAction( &ACTIONS::pasteSpecial ) )
|
|
|
|
{
|
2022-07-29 22:02:35 +01:00
|
|
|
DIALOG_PASTE_SPECIAL dlg( m_frame, &mode, defaultRef );
|
2021-05-01 23:44:22 +01:00
|
|
|
|
2024-04-01 16:55:37 -07:00
|
|
|
if( clipItem->Type() != PCB_T )
|
|
|
|
dlg.HideClearNets();
|
|
|
|
|
2021-05-01 23:44:22 +01:00
|
|
|
if( dlg.ShowModal() == wxID_CANCEL )
|
|
|
|
return 0;
|
2024-04-01 16:55:37 -07:00
|
|
|
|
|
|
|
clear_nets = dlg.GetClearNets();
|
2021-05-01 23:44:22 +01:00
|
|
|
}
|
|
|
|
|
2020-03-10 23:05:34 +00:00
|
|
|
if( clipItem->Type() == PCB_T )
|
|
|
|
{
|
2024-04-01 16:55:37 -07:00
|
|
|
BOARD* clipBoard = static_cast<BOARD*>( clipItem );
|
|
|
|
|
|
|
|
if( isFootprintEditor || clear_nets )
|
2020-03-10 23:05:34 +00:00
|
|
|
{
|
2024-04-01 16:55:37 -07:00
|
|
|
for( BOARD_CONNECTED_ITEM* item : clipBoard->AllConnectedItems() )
|
2020-03-10 23:05:34 +00:00
|
|
|
item->SetNet( NETINFO_LIST::OrphanedItem() );
|
|
|
|
}
|
|
|
|
else
|
2020-11-08 21:29:04 +00:00
|
|
|
{
|
2024-04-01 16:55:37 -07:00
|
|
|
clipBoard->MapNets( m_frame->GetBoard() );
|
2020-11-08 21:29:04 +00:00
|
|
|
}
|
2020-03-10 23:05:34 +00:00
|
|
|
}
|
|
|
|
|
2024-10-13 14:57:53 +08:00
|
|
|
bool cancelled = false;
|
2017-05-18 19:21:30 +02:00
|
|
|
|
|
|
|
switch( clipItem->Type() )
|
|
|
|
{
|
|
|
|
case PCB_T:
|
2017-09-22 17:17:38 +02:00
|
|
|
{
|
2020-01-13 14:19:44 +00:00
|
|
|
BOARD* clipBoard = static_cast<BOARD*>( clipItem );
|
2019-10-31 15:25:39 +00:00
|
|
|
|
2020-11-08 21:29:04 +00:00
|
|
|
if( isFootprintEditor )
|
2017-05-18 19:21:30 +02:00
|
|
|
{
|
2020-11-13 15:15:52 +00:00
|
|
|
FOOTPRINT* editorFootprint = board()->GetFirstFootprint();
|
2019-10-31 15:25:39 +00:00
|
|
|
std::vector<BOARD_ITEM*> pastedItems;
|
|
|
|
|
2022-11-11 17:09:25 +00:00
|
|
|
for( PCB_GROUP* group : clipBoard->Groups() )
|
|
|
|
{
|
2022-11-20 22:51:28 +00:00
|
|
|
group->SetParent( editorFootprint );
|
2022-11-11 17:09:25 +00:00
|
|
|
pastedItems.push_back( group );
|
|
|
|
}
|
|
|
|
|
2024-03-25 17:37:40 -04:00
|
|
|
clipBoard->RemoveAll( { PCB_GROUP_T } );
|
2022-11-11 17:09:25 +00:00
|
|
|
|
2020-11-13 15:15:52 +00:00
|
|
|
for( FOOTPRINT* clipFootprint : clipBoard->Footprints() )
|
2020-11-08 21:29:04 +00:00
|
|
|
pasteFootprintItemsToFootprintEditor( clipFootprint, board(), pastedItems );
|
2019-10-31 15:25:39 +00:00
|
|
|
|
|
|
|
for( BOARD_ITEM* clipDrawItem : clipBoard->Drawings() )
|
|
|
|
{
|
2023-03-30 12:49:23 +01:00
|
|
|
switch( clipDrawItem->Type() )
|
2019-10-31 15:25:39 +00:00
|
|
|
{
|
2023-03-30 12:49:23 +01:00
|
|
|
case PCB_TEXT_T:
|
|
|
|
case PCB_TEXTBOX_T:
|
2024-03-27 16:19:18 +00:00
|
|
|
case PCB_TABLE_T:
|
2023-03-30 12:49:23 +01:00
|
|
|
case PCB_SHAPE_T:
|
|
|
|
case PCB_DIM_ALIGNED_T:
|
|
|
|
case PCB_DIM_CENTER_T:
|
|
|
|
case PCB_DIM_LEADER_T:
|
|
|
|
case PCB_DIM_ORTHOGONAL_T:
|
|
|
|
case PCB_DIM_RADIAL_T:
|
|
|
|
clipDrawItem->SetParent( editorFootprint );
|
|
|
|
pastedItems.push_back( clipDrawItem );
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2024-03-27 16:19:18 +00:00
|
|
|
// Everything we *didn't* put into pastedItems is going to get nuked, so
|
|
|
|
// make sure it's not still included in its parent group.
|
2025-04-01 14:20:03 -04:00
|
|
|
if( EDA_GROUP* parentGroup = clipDrawItem->GetParentGroup() )
|
2022-11-11 17:09:25 +00:00
|
|
|
parentGroup->RemoveItem( clipDrawItem );
|
2019-10-31 15:25:39 +00:00
|
|
|
|
2023-03-30 12:49:23 +01:00
|
|
|
break;
|
2022-01-30 10:52:52 +00:00
|
|
|
}
|
2019-10-31 15:25:39 +00:00
|
|
|
}
|
|
|
|
|
2024-03-27 16:19:18 +00:00
|
|
|
// NB: PCB_SHAPE_T actually removes everything in Drawings() (including PCB_TEXTs,
|
|
|
|
// PCB_TABLES, dimensions, etc.), not just PCB_SHAPEs.)
|
2024-03-25 17:37:40 -04:00
|
|
|
clipBoard->RemoveAll( { PCB_SHAPE_T } );
|
2023-03-30 12:49:23 +01:00
|
|
|
|
2023-03-17 23:16:48 +00:00
|
|
|
clipBoard->Visit(
|
|
|
|
[&]( EDA_ITEM* item, void* testData )
|
|
|
|
{
|
2025-06-08 23:13:58 +01:00
|
|
|
if( item->IsBOARD_ITEM() )
|
|
|
|
{
|
|
|
|
// Anything still on the clipboard didn't get copied and needs to be
|
|
|
|
// removed from the pasted groups.
|
|
|
|
BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
|
|
|
|
EDA_GROUP* parentGroup = boardItem->GetParentGroup();
|
2023-03-17 23:16:48 +00:00
|
|
|
|
2025-06-08 23:13:58 +01:00
|
|
|
if( parentGroup )
|
|
|
|
parentGroup->RemoveItem( boardItem );
|
|
|
|
}
|
2023-03-17 23:16:48 +00:00
|
|
|
|
|
|
|
return INSPECT_RESULT::CONTINUE;
|
|
|
|
},
|
|
|
|
nullptr, GENERAL_COLLECTOR::AllBoardItems );
|
|
|
|
|
2019-10-31 15:25:39 +00:00
|
|
|
delete clipBoard;
|
|
|
|
|
2022-09-09 21:01:34 +01:00
|
|
|
pruneItemLayers( pastedItems );
|
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
cancelled = !placeBoardItems( &commit, pastedItems, true, true, mode == PASTE_MODE::UNIQUE_ANNOTATIONS,
|
|
|
|
false );
|
2017-05-18 19:21:30 +02:00
|
|
|
}
|
2024-05-03 18:37:13 +01:00
|
|
|
else // isBoardEditor
|
2019-10-31 15:25:39 +00:00
|
|
|
{
|
2025-02-07 17:47:21 +00:00
|
|
|
// Fixup footprint component classes
|
2025-04-12 20:49:19 +01:00
|
|
|
for( FOOTPRINT* fp : clipBoard->Footprints() )
|
2025-02-07 17:47:21 +00:00
|
|
|
{
|
2025-04-12 20:49:19 +01:00
|
|
|
fp->ResolveComponentClassNames( board(), fp->GetTransientComponentClassNames() );
|
|
|
|
fp->ClearTransientComponentClassNames();
|
2025-02-07 17:47:21 +00:00
|
|
|
}
|
|
|
|
|
2022-07-29 22:02:35 +01:00
|
|
|
if( mode == PASTE_MODE::REMOVE_ANNOTATIONS )
|
2021-05-01 23:44:22 +01:00
|
|
|
{
|
2025-04-12 20:49:19 +01:00
|
|
|
for( FOOTPRINT* fp : clipBoard->Footprints() )
|
|
|
|
fp->SetReference( defaultRef );
|
2021-05-01 23:44:22 +01:00
|
|
|
}
|
|
|
|
|
2025-08-21 11:23:33 +01:00
|
|
|
cancelled = !placeBoardItems( &commit, clipBoard, true, mode == PASTE_MODE::UNIQUE_ANNOTATIONS,
|
|
|
|
false );
|
2019-10-31 15:25:39 +00:00
|
|
|
}
|
2019-10-17 18:03:43 +01:00
|
|
|
|
2017-05-18 19:21:30 +02:00
|
|
|
break;
|
2017-09-22 17:17:38 +02:00
|
|
|
}
|
|
|
|
|
2020-11-13 12:21:02 +00:00
|
|
|
case PCB_FOOTPRINT_T:
|
2017-09-22 17:17:38 +02:00
|
|
|
{
|
2020-11-13 15:15:52 +00:00
|
|
|
FOOTPRINT* clipFootprint = static_cast<FOOTPRINT*>( clipItem );
|
2019-10-31 15:25:39 +00:00
|
|
|
std::vector<BOARD_ITEM*> pastedItems;
|
2017-09-22 17:17:38 +02:00
|
|
|
|
2020-11-08 21:29:04 +00:00
|
|
|
if( isFootprintEditor )
|
2017-05-18 19:21:30 +02:00
|
|
|
{
|
2020-11-08 21:29:04 +00:00
|
|
|
pasteFootprintItemsToFootprintEditor( clipFootprint, board(), pastedItems );
|
|
|
|
delete clipFootprint;
|
2017-05-18 19:21:30 +02:00
|
|
|
}
|
2017-09-22 17:17:38 +02:00
|
|
|
else
|
2017-05-18 19:21:30 +02:00
|
|
|
{
|
2022-07-29 22:02:35 +01:00
|
|
|
if( mode == PASTE_MODE::REMOVE_ANNOTATIONS )
|
2021-05-01 23:44:22 +01:00
|
|
|
clipFootprint->SetReference( defaultRef );
|
|
|
|
|
2020-11-08 21:29:04 +00:00
|
|
|
clipFootprint->SetParent( board() );
|
2025-08-21 11:23:33 +01:00
|
|
|
clipFootprint->ResolveComponentClassNames( board(), clipFootprint->GetTransientComponentClassNames() );
|
2025-02-07 17:47:21 +00:00
|
|
|
clipFootprint->ClearTransientComponentClassNames();
|
2020-11-08 21:29:04 +00:00
|
|
|
pastedItems.push_back( clipFootprint );
|
2017-05-18 19:21:30 +02:00
|
|
|
}
|
2017-09-22 17:17:38 +02:00
|
|
|
|
2022-09-09 21:01:34 +01:00
|
|
|
pruneItemLayers( pastedItems );
|
|
|
|
|
2025-08-21 11:23:33 +01:00
|
|
|
cancelled = !placeBoardItems( &commit, pastedItems, true, true, mode == PASTE_MODE::UNIQUE_ANNOTATIONS,
|
|
|
|
false );
|
2017-05-18 19:21:30 +02:00
|
|
|
break;
|
2017-09-22 17:17:38 +02:00
|
|
|
}
|
2018-05-08 10:36:50 +02:00
|
|
|
|
2017-05-18 19:21:30 +02:00
|
|
|
default:
|
2017-09-22 17:17:38 +02:00
|
|
|
m_frame->DisplayToolMsg( _( "Invalid clipboard contents" ) );
|
2017-05-18 19:21:30 +02:00
|
|
|
break;
|
|
|
|
}
|
2018-05-08 10:36:50 +02:00
|
|
|
|
2023-06-27 16:52:50 +01:00
|
|
|
if( cancelled )
|
|
|
|
commit.Revert();
|
|
|
|
else
|
|
|
|
commit.Push( _( "Paste" ) );
|
|
|
|
|
2017-05-18 19:21:30 +02:00
|
|
|
return 1;
|
2017-04-15 18:08:23 +02:00
|
|
|
}
|
2015-06-18 17:51:51 +02:00
|
|
|
|
2018-05-08 10:36:50 +02:00
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::AppendBoardFromFile( const TOOL_EVENT& aEvent )
|
2015-08-07 18:24:42 +02:00
|
|
|
{
|
|
|
|
wxString fileName;
|
|
|
|
|
|
|
|
PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
|
|
|
|
|
|
|
|
if( !editFrame )
|
2017-05-18 19:21:30 +02:00
|
|
|
return 1;
|
2015-08-07 18:24:42 +02:00
|
|
|
|
|
|
|
// Pick a file to append
|
2023-11-29 14:01:13 +00:00
|
|
|
if( !AskLoadBoardFileName( editFrame, &fileName, KICTL_KICAD_ONLY ) )
|
2017-05-18 19:21:30 +02:00
|
|
|
return 1;
|
2015-08-07 18:24:42 +02:00
|
|
|
|
2025-04-12 20:49:19 +01:00
|
|
|
PCB_IO_MGR::PCB_FILE_T pluginType = PCB_IO_MGR::FindPluginTypeFromBoardPath( fileName, KICTL_KICAD_ONLY );
|
2023-12-27 20:39:29 +00:00
|
|
|
IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( pluginType ) );
|
2015-08-07 18:24:42 +02:00
|
|
|
|
2023-08-26 22:28:53 +03:00
|
|
|
if( !pi )
|
|
|
|
return 1;
|
|
|
|
|
2017-04-15 18:08:23 +02:00
|
|
|
return AppendBoard( *pi, fileName );
|
|
|
|
}
|
2017-05-18 19:21:30 +02:00
|
|
|
|
2018-05-08 10:36:50 +02:00
|
|
|
|
2025-03-20 11:57:59 -04:00
|
|
|
int PCB_CONTROL::AppendDesignBlock( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
|
|
|
|
|
|
|
|
if( !editFrame )
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if( !editFrame->GetDesignBlockPane()->GetSelectedLibId().IsValid() )
|
|
|
|
return 1;
|
|
|
|
|
2025-05-25 11:18:50 +01:00
|
|
|
DESIGN_BLOCK_PANE* designBlockPane = editFrame->GetDesignBlockPane();
|
|
|
|
std::unique_ptr<DESIGN_BLOCK> designBlock( designBlockPane->GetDesignBlock( designBlockPane->GetSelectedLibId(),
|
|
|
|
true, true ) );
|
2025-03-20 11:57:59 -04:00
|
|
|
|
2025-05-25 11:18:50 +01:00
|
|
|
if( !designBlock || designBlock->GetBoardFile().IsEmpty() )
|
2025-05-25 09:28:37 +01:00
|
|
|
return 1;
|
2025-03-20 11:57:59 -04:00
|
|
|
|
|
|
|
PCB_IO_MGR::PCB_FILE_T pluginType = PCB_IO_MGR::KICAD_SEXP;
|
|
|
|
IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( pluginType ) );
|
|
|
|
|
|
|
|
if( !pi )
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
bool repeatPlacement = false;
|
|
|
|
|
|
|
|
if( APP_SETTINGS_BASE* cfg = editFrame->config() )
|
|
|
|
repeatPlacement = cfg->m_DesignBlockChooserPanel.repeated_placement;
|
|
|
|
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
2025-08-20 10:11:52 -04:00
|
|
|
ret = AppendBoard( *pi, designBlock->GetBoardFile(), designBlock.get() );
|
2025-03-20 11:57:59 -04:00
|
|
|
} while( repeatPlacement && ret == 0 );
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
int PCB_CONTROL::ApplyDesignBlockLayout( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
|
|
|
|
|
|
|
|
if( !editFrame )
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
BOARD* brd = board();
|
|
|
|
|
|
|
|
if( !brd )
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
// Need to have a group selected and it needs to have a linked design block
|
|
|
|
PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
PCB_SELECTION selection = selTool->GetSelection();
|
|
|
|
|
|
|
|
if( selection.Size() != 1 || selection[0]->Type() != PCB_GROUP_T )
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
PCB_GROUP* group = static_cast<PCB_GROUP*>( selection[0] );
|
|
|
|
|
|
|
|
if( !group->HasDesignBlockLink() )
|
|
|
|
return 1;
|
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
BOARD_COMMIT tempCommit( m_frame );
|
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
std::set<EDA_ITEM*> originalItems;
|
|
|
|
// Apply MCT_SKIP_STRUCT to every EDA_ITEM on the board so we know what is not part of the design block
|
|
|
|
// Can't use SKIP_STRUCT as that is used and cleared by the temporary board appending
|
|
|
|
brd->Visit( []( EDA_ITEM* item, void* )
|
|
|
|
{
|
|
|
|
item->SetFlags( MCT_SKIP_STRUCT );
|
|
|
|
return INSPECT_RESULT::CONTINUE;
|
|
|
|
},
|
|
|
|
nullptr, GENERAL_COLLECTOR::AllBoardItems );
|
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
int ret = 1;
|
|
|
|
|
2025-07-16 10:40:27 -04:00
|
|
|
bool skipMove = true;
|
2025-08-19 13:57:07 -04:00
|
|
|
|
|
|
|
// If we succeeded in placing the linked design block, we're ready to apply the multichannel tool
|
2025-07-16 10:40:27 -04:00
|
|
|
if( m_toolMgr->RunSynchronousAction( PCB_ACTIONS::placeLinkedDesignBlock, &tempCommit, &skipMove ) )
|
2025-08-19 13:57:07 -04:00
|
|
|
{
|
2025-08-19 13:57:08 -04:00
|
|
|
// Lambda for the bounding box of all the components
|
2025-08-19 13:57:07 -04:00
|
|
|
auto generateBoundingBox = [&]( std::unordered_set<EDA_ITEM*> aItems )
|
|
|
|
{
|
|
|
|
std::vector<VECTOR2I> bbCorners;
|
|
|
|
bbCorners.reserve( aItems.size() * 4 );
|
|
|
|
|
|
|
|
for( auto item : aItems )
|
|
|
|
{
|
|
|
|
const BOX2I bb = item->GetBoundingBox().GetInflated( 100000 );
|
|
|
|
KIGEOM::CollectBoxCorners( bb, bbCorners );
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<VECTOR2I> hullVertices;
|
|
|
|
BuildConvexHull( hullVertices, bbCorners );
|
|
|
|
|
|
|
|
SHAPE_LINE_CHAIN hull( hullVertices );
|
|
|
|
|
|
|
|
// Make the newly computed convex hull use only 90 degree segments
|
|
|
|
return KIGEOM::RectifyPolygon( hull );
|
|
|
|
};
|
|
|
|
|
|
|
|
// Build a rule area that contains all the components in the design block,
|
|
|
|
// meaning all items without SKIP_STRUCT set.
|
|
|
|
RULE_AREA dbRA;
|
|
|
|
|
2025-08-19 13:57:08 -04:00
|
|
|
dbRA.m_sourceType = PLACEMENT_SOURCE_T::DESIGN_BLOCK;
|
2025-08-19 13:57:07 -04:00
|
|
|
dbRA.m_generateEnabled = true;
|
|
|
|
|
|
|
|
// Add all components that aren't marked MCT_SKIP_STRUCT to ra.m_components
|
|
|
|
brd->Visit(
|
|
|
|
[&]( EDA_ITEM* item, void* data )
|
|
|
|
{
|
|
|
|
if( !item->HasFlag( MCT_SKIP_STRUCT ) )
|
|
|
|
{
|
2025-08-19 13:57:08 -04:00
|
|
|
dbRA.m_designBlockItems.insert( item );
|
2025-08-19 13:57:07 -04:00
|
|
|
|
|
|
|
if( item->Type() == PCB_FOOTPRINT_T )
|
|
|
|
dbRA.m_components.insert( static_cast<FOOTPRINT*>( item ) );
|
|
|
|
}
|
|
|
|
return INSPECT_RESULT::CONTINUE;
|
|
|
|
},
|
|
|
|
nullptr, GENERAL_COLLECTOR::AllBoardItems );
|
|
|
|
|
2025-08-19 13:57:08 -04:00
|
|
|
dbRA.m_zone = new ZONE( board() );
|
2025-08-19 13:57:07 -04:00
|
|
|
//dbRA.m_area->SetZoneName( wxString::Format( wxT( "design-block-source-%s" ), group->GetDesignBlockLibId().GetUniStringLibId() ) );
|
2025-08-19 13:57:08 -04:00
|
|
|
dbRA.m_zone->SetIsRuleArea( true );
|
|
|
|
dbRA.m_zone->SetLayerSet( LSET::AllCuMask() );
|
|
|
|
dbRA.m_zone->SetPlacementAreaEnabled( true );
|
|
|
|
dbRA.m_zone->SetDoNotAllowZoneFills( false );
|
|
|
|
dbRA.m_zone->SetDoNotAllowVias( false );
|
|
|
|
dbRA.m_zone->SetDoNotAllowTracks( false );
|
|
|
|
dbRA.m_zone->SetDoNotAllowPads( false );
|
|
|
|
dbRA.m_zone->SetDoNotAllowFootprints( false );
|
2025-08-19 13:57:08 -04:00
|
|
|
dbRA.m_zone->SetPlacementAreaSourceType( dbRA.m_sourceType );
|
2025-08-19 13:57:08 -04:00
|
|
|
dbRA.m_zone->SetPlacementAreaSource( group->GetDesignBlockLibId().GetUniStringLibId() );
|
|
|
|
dbRA.m_zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH );
|
2025-08-19 13:57:08 -04:00
|
|
|
dbRA.m_zone->AddPolygon( generateBoundingBox( dbRA.m_designBlockItems ) );
|
2025-08-19 13:57:08 -04:00
|
|
|
dbRA.m_center = dbRA.m_zone->Outline()->COutline( 0 ).Centre();
|
|
|
|
tempCommit.Add( dbRA.m_zone );
|
2025-08-19 13:57:07 -04:00
|
|
|
|
|
|
|
// Create the destination rule area for the group
|
|
|
|
RULE_AREA destRA;
|
|
|
|
|
|
|
|
destRA.m_sourceType = PLACEMENT_SOURCE_T::GROUP_PLACEMENT;
|
|
|
|
|
|
|
|
// Add all the design block group footprints to the destination rule area
|
|
|
|
for( EDA_ITEM* item : group->GetItems() )
|
|
|
|
{
|
|
|
|
if( item->Type() == PCB_FOOTPRINT_T )
|
|
|
|
{
|
|
|
|
FOOTPRINT* fp = static_cast<FOOTPRINT*>( item );
|
|
|
|
|
|
|
|
// If the footprint is locked, we can't place it
|
|
|
|
if( fp->IsLocked() )
|
|
|
|
{
|
|
|
|
wxString msg;
|
|
|
|
msg.Printf( _( "Footprint %s is locked and cannot be placed." ), fp->GetReference() );
|
|
|
|
m_frame->GetInfoBar()->ShowMessageFor( msg, 5000, wxICON_WARNING );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
destRA.m_components.insert( fp );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-19 13:57:08 -04:00
|
|
|
destRA.m_zone = new ZONE( board() );
|
|
|
|
destRA.m_zone->SetZoneName(
|
2025-08-19 13:57:07 -04:00
|
|
|
wxString::Format( wxT( "design-block-dest-%s" ), group->GetDesignBlockLibId().GetUniStringLibId() ) );
|
2025-08-19 13:57:08 -04:00
|
|
|
destRA.m_zone->SetIsRuleArea( true );
|
|
|
|
destRA.m_zone->SetLayerSet( LSET::AllCuMask() );
|
|
|
|
destRA.m_zone->SetPlacementAreaEnabled( true );
|
|
|
|
destRA.m_zone->SetDoNotAllowZoneFills( false );
|
|
|
|
destRA.m_zone->SetDoNotAllowVias( false );
|
|
|
|
destRA.m_zone->SetDoNotAllowTracks( false );
|
|
|
|
destRA.m_zone->SetDoNotAllowPads( false );
|
|
|
|
destRA.m_zone->SetDoNotAllowFootprints( false );
|
2025-08-19 13:57:08 -04:00
|
|
|
destRA.m_zone->SetPlacementAreaSourceType( destRA.m_sourceType );
|
2025-08-19 13:57:08 -04:00
|
|
|
destRA.m_zone->SetPlacementAreaSource( group->GetName() );
|
|
|
|
destRA.m_zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH );
|
|
|
|
destRA.m_zone->AddPolygon( generateBoundingBox( group->GetItems() ) );
|
|
|
|
destRA.m_center = destRA.m_zone->Outline()->COutline( 0 ).Centre();
|
|
|
|
tempCommit.Add( dbRA.m_zone );
|
2025-08-19 13:57:07 -04:00
|
|
|
|
|
|
|
// Use the multichannel tool to repeat the layout
|
|
|
|
MULTICHANNEL_TOOL* mct = m_toolMgr->GetTool<MULTICHANNEL_TOOL>();
|
|
|
|
|
|
|
|
ret = mct->RepeatLayout( aEvent, dbRA, destRA );
|
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
// Get rid of the temporary design blocks and rule areas
|
|
|
|
tempCommit.Revert();
|
|
|
|
|
2025-08-19 13:57:08 -04:00
|
|
|
delete dbRA.m_zone;
|
|
|
|
delete destRA.m_zone;
|
2025-08-19 13:57:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// We're done, remove SKIP_STRUCT
|
|
|
|
brd->Visit( []( EDA_ITEM* item, void* )
|
|
|
|
{
|
|
|
|
item->ClearFlags( MCT_SKIP_STRUCT );
|
|
|
|
return INSPECT_RESULT::CONTINUE;
|
|
|
|
},
|
|
|
|
nullptr, GENERAL_COLLECTOR::AllBoardItems );
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2025-03-20 11:57:59 -04:00
|
|
|
|
2025-05-08 13:30:31 -04:00
|
|
|
int PCB_CONTROL::PlaceLinkedDesignBlock( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
|
|
|
|
|
|
|
|
if( !editFrame )
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
// Need to have a group selected and it needs to have a linked design block
|
|
|
|
PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
PCB_SELECTION selection = selTool->GetSelection();
|
|
|
|
|
|
|
|
if( selection.Size() != 1 || selection[0]->Type() != PCB_GROUP_T )
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
PCB_GROUP* group = static_cast<PCB_GROUP*>( selection[0] );
|
|
|
|
|
|
|
|
if( !group->HasDesignBlockLink() )
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
// Get the associated design block
|
2025-05-25 11:18:50 +01:00
|
|
|
DESIGN_BLOCK_PANE* designBlockPane = editFrame->GetDesignBlockPane();
|
|
|
|
std::unique_ptr<DESIGN_BLOCK> designBlock( designBlockPane->GetDesignBlock( group->GetDesignBlockLibId(),
|
|
|
|
true, true ) );
|
2025-05-08 13:30:31 -04:00
|
|
|
|
|
|
|
if( !designBlock )
|
|
|
|
{
|
|
|
|
wxString msg;
|
|
|
|
msg.Printf( _( "Could not find design block %s." ), group->GetDesignBlockLibId().GetUniStringLibId() );
|
|
|
|
m_frame->GetInfoBar()->ShowMessageFor( msg, 5000, wxICON_WARNING );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2025-05-25 11:18:50 +01:00
|
|
|
if( designBlock->GetBoardFile().IsEmpty() )
|
2025-05-08 13:30:31 -04:00
|
|
|
{
|
|
|
|
wxString msg;
|
|
|
|
msg.Printf( _( "Design block %s does not have a board file." ),
|
|
|
|
group->GetDesignBlockLibId().GetUniStringLibId() );
|
|
|
|
m_frame->GetInfoBar()->ShowMessageFor( msg, 5000, wxICON_WARNING );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PCB_IO_MGR::PCB_FILE_T pluginType = PCB_IO_MGR::KICAD_SEXP;
|
|
|
|
IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( pluginType ) );
|
|
|
|
|
|
|
|
if( !pi )
|
|
|
|
return 1;
|
|
|
|
|
2025-07-16 10:40:27 -04:00
|
|
|
if( aEvent.Parameter<bool*>() != nullptr )
|
2025-08-20 10:11:52 -04:00
|
|
|
return AppendBoard( *pi, designBlock->GetBoardFile(), designBlock.get(),
|
|
|
|
static_cast<BOARD_COMMIT*>( aEvent.Commit() ), *aEvent.Parameter<bool*>() );
|
2025-08-19 13:57:07 -04:00
|
|
|
else
|
2025-08-20 10:11:52 -04:00
|
|
|
return AppendBoard( *pi, designBlock->GetBoardFile(), designBlock.get() );
|
2025-05-08 13:30:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-05-15 14:26:37 -04:00
|
|
|
int PCB_CONTROL::SaveToLinkedDesignBlock( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
|
|
|
|
|
|
|
|
if( !editFrame )
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
// Need to have a group selected and it needs to have a linked design block
|
|
|
|
PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
PCB_SELECTION selection = selTool->GetSelection();
|
|
|
|
|
|
|
|
if( selection.Size() != 1 || selection[0]->Type() != PCB_GROUP_T )
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
PCB_GROUP* group = static_cast<PCB_GROUP*>( selection[0] );
|
|
|
|
|
|
|
|
if( !group->HasDesignBlockLink() )
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
// Get the associated design block
|
2025-05-25 11:18:50 +01:00
|
|
|
DESIGN_BLOCK_PANE* designBlockPane = editFrame->GetDesignBlockPane();
|
|
|
|
std::unique_ptr<DESIGN_BLOCK> designBlock( designBlockPane->GetDesignBlock( group->GetDesignBlockLibId(),
|
|
|
|
true, true ) );
|
2025-05-15 14:26:37 -04:00
|
|
|
|
|
|
|
if( !designBlock )
|
|
|
|
{
|
|
|
|
wxString msg;
|
|
|
|
msg.Printf( _( "Could not find design block %s." ), group->GetDesignBlockLibId().GetUniStringLibId() );
|
|
|
|
m_frame->GetInfoBar()->ShowMessageFor( msg, 5000, wxICON_WARNING );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
editFrame->GetDesignBlockPane()->SelectLibId( group->GetDesignBlockLibId() );
|
|
|
|
|
|
|
|
return m_toolMgr->RunAction( PCB_ACTIONS::saveSelectionToDesignBlock ) ? 1 : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-05-30 06:33:59 -07:00
|
|
|
template<typename T>
|
2024-03-25 17:37:40 -04:00
|
|
|
static void moveUnflaggedItems( const std::deque<T>& aList, std::vector<BOARD_ITEM*>& aTarget,
|
2020-07-01 13:38:18 +01:00
|
|
|
bool aIsNew )
|
2019-05-30 06:33:59 -07:00
|
|
|
{
|
|
|
|
std::copy_if( aList.begin(), aList.end(), std::back_inserter( aTarget ),
|
2020-05-09 14:14:00 +01:00
|
|
|
[aIsNew]( T aItem )
|
2019-07-16 00:44:01 +01:00
|
|
|
{
|
2020-06-27 17:06:01 +01:00
|
|
|
bool doCopy = ( aItem->GetFlags() & SKIP_STRUCT ) == 0;
|
2020-05-09 14:14:00 +01:00
|
|
|
|
2020-06-27 17:06:01 +01:00
|
|
|
aItem->ClearFlags( SKIP_STRUCT );
|
2020-05-09 14:14:00 +01:00
|
|
|
aItem->SetFlags( aIsNew ? IS_NEW : 0 );
|
|
|
|
|
|
|
|
return doCopy;
|
2019-05-30 06:33:59 -07:00
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-03-25 17:37:40 -04:00
|
|
|
template<typename T>
|
|
|
|
static void moveUnflaggedItems( const std::vector<T>& aList, std::vector<BOARD_ITEM*>& aTarget,
|
|
|
|
bool aIsNew )
|
2018-05-12 20:48:50 +02:00
|
|
|
{
|
2024-03-25 17:37:40 -04:00
|
|
|
std::copy_if( aList.begin(), aList.end(), std::back_inserter( aTarget ),
|
|
|
|
[aIsNew]( T aItem )
|
|
|
|
{
|
|
|
|
bool doCopy = ( aItem->GetFlags() & SKIP_STRUCT ) == 0;
|
2018-05-12 20:48:50 +02:00
|
|
|
|
2024-03-25 17:37:40 -04:00
|
|
|
aItem->ClearFlags( SKIP_STRUCT );
|
|
|
|
aItem->SetFlags( aIsNew ? IS_NEW : 0 );
|
2018-05-12 20:48:50 +02:00
|
|
|
|
2024-03-25 17:37:40 -04:00
|
|
|
return doCopy;
|
|
|
|
} );
|
2018-05-12 20:48:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-06-27 16:52:50 +01:00
|
|
|
bool PCB_CONTROL::placeBoardItems( BOARD_COMMIT* aCommit, BOARD* aBoard, bool aAnchorAtOrigin,
|
2025-08-19 13:57:07 -04:00
|
|
|
bool aReannotateDuplicates, bool aSkipMove )
|
2018-05-08 11:21:55 +02:00
|
|
|
{
|
2017-11-11 09:09:24 +01:00
|
|
|
// items are new if the current board is not the board source
|
|
|
|
bool isNew = board() != aBoard;
|
2018-05-08 11:21:55 +02:00
|
|
|
std::vector<BOARD_ITEM*> items;
|
|
|
|
|
2020-07-01 13:38:18 +01:00
|
|
|
moveUnflaggedItems( aBoard->Tracks(), items, isNew );
|
2020-11-12 23:50:33 +00:00
|
|
|
moveUnflaggedItems( aBoard->Footprints(), items, isNew );
|
2020-07-01 13:38:18 +01:00
|
|
|
moveUnflaggedItems( aBoard->Drawings(), items, isNew );
|
|
|
|
moveUnflaggedItems( aBoard->Zones(), items, isNew );
|
2018-05-08 11:21:55 +02:00
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
// Subtlety: When selecting a group via the mouse,
|
2020-12-16 13:31:32 +00:00
|
|
|
// PCB_SELECTION_TOOL::highlightInternal runs, which does a SetSelected() on all
|
|
|
|
// descendants. In PCB_CONTROL::placeBoardItems, below, we skip that and
|
2020-08-11 19:37:07 +00:00
|
|
|
// mark items non-recursively. That works because the saving of the
|
2021-07-19 19:56:05 -04:00
|
|
|
// selection created aBoard that has the group and all descendants in it.
|
2020-08-11 19:37:07 +00:00
|
|
|
moveUnflaggedItems( aBoard->Groups(), items, isNew );
|
|
|
|
|
2024-01-16 23:37:12 +00:00
|
|
|
moveUnflaggedItems( aBoard->Generators(), items, isNew );
|
|
|
|
|
2024-03-25 17:37:40 -04:00
|
|
|
if( isNew )
|
|
|
|
aBoard->RemoveAll();
|
|
|
|
|
|
|
|
// Reparent before calling pruneItemLayers, as SetLayer can have a dependence on the
|
|
|
|
// item's parent board being set correctly.
|
|
|
|
if( isNew )
|
|
|
|
{
|
|
|
|
for( BOARD_ITEM* item : items )
|
|
|
|
item->SetParent( board() );
|
|
|
|
}
|
|
|
|
|
2022-09-09 21:01:34 +01:00
|
|
|
pruneItemLayers( items );
|
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
return placeBoardItems( aCommit, items, isNew, aAnchorAtOrigin, aReannotateDuplicates, aSkipMove );
|
2017-09-22 17:17:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
bool PCB_CONTROL::placeBoardItems( BOARD_COMMIT* aCommit, std::vector<BOARD_ITEM*>& aItems, bool aIsNew,
|
|
|
|
bool aAnchorAtOrigin, bool aReannotateDuplicates, bool aSkipMove )
|
2017-09-22 17:17:38 +02:00
|
|
|
{
|
2025-04-02 11:01:22 -04:00
|
|
|
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
2017-09-22 17:17:38 +02:00
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
2021-04-03 19:08:04 +01:00
|
|
|
|
|
|
|
std::vector<BOARD_ITEM*> itemsToSel;
|
|
|
|
itemsToSel.reserve( aItems.size() );
|
2017-09-22 17:17:38 +02:00
|
|
|
|
2020-07-27 18:56:15 +01:00
|
|
|
for( BOARD_ITEM* item : aItems )
|
2017-09-22 17:17:38 +02:00
|
|
|
{
|
2020-07-30 18:05:15 +01:00
|
|
|
if( aIsNew )
|
|
|
|
{
|
|
|
|
const_cast<KIID&>( item->m_Uuid ) = KIID();
|
|
|
|
|
2025-03-27 10:43:44 -04:00
|
|
|
item->RunOnChildren(
|
2024-11-25 19:09:41 +00:00
|
|
|
[]( BOARD_ITEM* aChild )
|
|
|
|
{
|
|
|
|
const_cast<KIID&>( aChild->m_Uuid ) = KIID();
|
2025-03-27 10:43:44 -04:00
|
|
|
},
|
|
|
|
RECURSE_MODE::RECURSE );
|
2024-11-25 19:09:41 +00:00
|
|
|
|
2025-05-19 21:36:46 +01:00
|
|
|
// While BOARD_COMMIT::Push() will add any new items to the entered group,
|
|
|
|
// we need to do it earlier so that the previews while moving are correct.
|
|
|
|
if( PCB_GROUP* enteredGroup = selectionTool->GetEnteredGroup() )
|
|
|
|
{
|
|
|
|
if( item->IsGroupableType() && !item->GetParentGroup() )
|
|
|
|
{
|
2025-06-12 11:20:33 +01:00
|
|
|
aCommit->Modify( enteredGroup, nullptr, RECURSE_MODE::NO_RECURSE );
|
2025-05-19 21:36:46 +01:00
|
|
|
enteredGroup->AddItem( item );
|
|
|
|
}
|
|
|
|
}
|
2024-01-16 23:37:12 +00:00
|
|
|
|
|
|
|
item->SetParent( board() );
|
2020-11-04 00:36:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update item attributes if needed
|
2021-12-04 23:52:00 +00:00
|
|
|
if( BaseType( item->Type() ) == PCB_DIMENSION_T )
|
2020-11-04 00:36:27 +00:00
|
|
|
{
|
2023-03-05 13:27:22 +00:00
|
|
|
static_cast<PCB_DIMENSION_BASE*>( item )->UpdateUnits();
|
2021-07-19 19:56:05 -04:00
|
|
|
}
|
2021-12-04 23:52:00 +00:00
|
|
|
else if( item->Type() == PCB_FOOTPRINT_T )
|
|
|
|
{
|
|
|
|
FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
|
2020-11-04 00:36:27 +00:00
|
|
|
|
2020-11-14 22:00:12 +00:00
|
|
|
// Update the footprint path with the new KIID path if the footprint is new
|
2020-11-04 00:36:27 +00:00
|
|
|
if( aIsNew )
|
2021-12-04 23:52:00 +00:00
|
|
|
footprint->SetPath( KIID_PATH() );
|
2020-11-04 00:36:27 +00:00
|
|
|
|
2021-12-04 23:52:00 +00:00
|
|
|
for( BOARD_ITEM* dwg : footprint->GraphicalItems() )
|
|
|
|
{
|
|
|
|
if( BaseType( dwg->Type() ) == PCB_DIMENSION_T )
|
2023-03-05 13:27:22 +00:00
|
|
|
static_cast<PCB_DIMENSION_BASE*>( dwg )->UpdateUnits();
|
2021-12-04 23:52:00 +00:00
|
|
|
}
|
2020-07-30 18:05:15 +01:00
|
|
|
}
|
|
|
|
|
2021-04-03 19:08:04 +01:00
|
|
|
// We only need to add the items that aren't inside a group currently selected
|
|
|
|
// to the selection. If an item is inside a group and that group is selected,
|
|
|
|
// then the selection tool will select it for us.
|
2025-04-01 14:20:03 -04:00
|
|
|
if( !item->GetParentGroup() || !alg::contains( aItems, item->GetParentGroup()->AsEdaItem() ) )
|
2021-04-03 19:08:04 +01:00
|
|
|
itemsToSel.push_back( item );
|
2017-09-22 17:17:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-03 19:08:04 +01:00
|
|
|
// Select the items that should be selected
|
2023-06-16 22:28:15 +01:00
|
|
|
EDA_ITEMS toSel( itemsToSel.begin(), itemsToSel.end() );
|
2025-04-02 11:01:22 -04:00
|
|
|
m_toolMgr->RunAction<EDA_ITEMS*>( ACTIONS::selectItems, &toSel );
|
2021-04-03 19:08:04 +01:00
|
|
|
|
2021-08-18 15:34:32 +02:00
|
|
|
// Reannotate duplicate footprints (make sense only in board editor )
|
2022-11-20 22:51:28 +00:00
|
|
|
if( aReannotateDuplicates && m_isBoardEditor )
|
2021-05-01 23:44:22 +01:00
|
|
|
m_toolMgr->GetTool<BOARD_REANNOTATE_TOOL>()->ReannotateDuplicatesInSelection();
|
2021-05-01 23:39:16 +01:00
|
|
|
|
2024-01-07 12:57:22 +00:00
|
|
|
for( BOARD_ITEM* item : aItems )
|
2021-05-01 23:39:16 +01:00
|
|
|
{
|
|
|
|
if( aIsNew )
|
2023-06-27 16:52:50 +01:00
|
|
|
aCommit->Add( item );
|
2021-05-01 23:39:16 +01:00
|
|
|
else
|
2023-06-27 16:52:50 +01:00
|
|
|
aCommit->Added( item );
|
2021-05-01 23:39:16 +01:00
|
|
|
}
|
|
|
|
|
2021-04-03 19:08:04 +01:00
|
|
|
PCB_SELECTION& selection = selectionTool->GetSelection();
|
|
|
|
|
2019-08-12 20:30:25 +01:00
|
|
|
if( selection.Size() > 0 )
|
|
|
|
{
|
2019-08-20 19:40:26 +01:00
|
|
|
if( aAnchorAtOrigin )
|
|
|
|
{
|
|
|
|
selection.SetReferencePoint( VECTOR2I( 0, 0 ) );
|
|
|
|
}
|
2025-06-08 23:13:58 +01:00
|
|
|
else if( BOARD_ITEM* item = dynamic_cast<BOARD_ITEM*>( selection.GetTopLeftItem() ) )
|
2019-08-20 19:40:26 +01:00
|
|
|
{
|
|
|
|
selection.SetReferencePoint( item->GetPosition() );
|
|
|
|
}
|
2019-08-12 20:30:25 +01:00
|
|
|
|
|
|
|
getViewControls()->SetCursorPosition( getViewControls()->GetMousePosition(), false );
|
2017-09-22 17:17:38 +02:00
|
|
|
|
2019-08-12 20:30:25 +01:00
|
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
2023-06-27 16:52:50 +01:00
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
if( !aSkipMove )
|
|
|
|
return m_toolMgr->RunSynchronousAction( PCB_ACTIONS::move, aCommit );
|
2019-08-12 20:30:25 +01:00
|
|
|
}
|
2017-09-22 17:17:38 +02:00
|
|
|
|
2023-06-27 16:52:50 +01:00
|
|
|
return true;
|
2017-09-22 17:17:38 +02:00
|
|
|
}
|
|
|
|
|
2017-11-11 09:09:24 +01:00
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
int PCB_CONTROL::AppendBoard( PCB_IO& pi, const wxString& fileName, DESIGN_BLOCK* aDesignBlock, BOARD_COMMIT* aCommit,
|
2025-07-16 10:40:27 -04:00
|
|
|
bool aSkipMove )
|
2017-04-15 18:08:23 +02:00
|
|
|
{
|
|
|
|
PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
|
2017-11-11 09:09:24 +01:00
|
|
|
|
2017-05-21 22:18:22 +02:00
|
|
|
if( !editFrame )
|
2017-05-18 19:21:30 +02:00
|
|
|
return 1;
|
|
|
|
|
2017-09-22 17:17:38 +02:00
|
|
|
BOARD* brd = board();
|
2017-11-11 09:09:24 +01:00
|
|
|
|
2017-09-22 17:17:38 +02:00
|
|
|
if( !brd )
|
2017-05-18 19:21:30 +02:00
|
|
|
return 1;
|
2015-08-07 18:24:42 +02:00
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
// Give ourselves a commit to work with if we weren't provided one
|
|
|
|
std::unique_ptr<BOARD_COMMIT> tempCommit;
|
|
|
|
BOARD_COMMIT* commit = aCommit;
|
|
|
|
|
|
|
|
if( !commit )
|
|
|
|
{
|
|
|
|
tempCommit = std::make_unique<BOARD_COMMIT>( editFrame );
|
|
|
|
commit = tempCommit.get();
|
|
|
|
}
|
2023-07-07 10:17:20 +02:00
|
|
|
|
2019-06-11 01:00:15 +01:00
|
|
|
// Mark existing items, in order to know what are the new items so we can select only
|
|
|
|
// the new items after loading
|
2021-06-11 22:07:02 +01:00
|
|
|
for( PCB_TRACK* track : brd->Tracks() )
|
2020-06-27 17:06:01 +01:00
|
|
|
track->SetFlags( SKIP_STRUCT );
|
2017-11-11 09:09:24 +01:00
|
|
|
|
2020-11-14 21:21:54 +00:00
|
|
|
for( FOOTPRINT* footprint : brd->Footprints() )
|
|
|
|
footprint->SetFlags( SKIP_STRUCT );
|
2017-11-11 09:09:24 +01:00
|
|
|
|
2020-11-14 21:21:54 +00:00
|
|
|
for( PCB_GROUP* group : brd->Groups() )
|
2020-08-11 19:37:07 +00:00
|
|
|
group->SetFlags( SKIP_STRUCT );
|
|
|
|
|
2020-11-14 21:21:54 +00:00
|
|
|
for( BOARD_ITEM* drawing : brd->Drawings() )
|
2020-06-27 17:06:01 +01:00
|
|
|
drawing->SetFlags( SKIP_STRUCT );
|
2017-11-11 09:09:24 +01:00
|
|
|
|
2020-11-14 21:21:54 +00:00
|
|
|
for( ZONE* zone : brd->Zones() )
|
2020-06-27 17:06:01 +01:00
|
|
|
zone->SetFlags( SKIP_STRUCT );
|
2015-08-07 18:24:42 +02:00
|
|
|
|
2024-02-13 10:14:43 -05:00
|
|
|
for( PCB_GENERATOR* generator : brd->Generators() )
|
|
|
|
generator->SetFlags( SKIP_STRUCT );
|
|
|
|
|
2020-08-19 19:29:09 +01:00
|
|
|
std::map<wxString, wxString> oldProperties = brd->GetProperties();
|
|
|
|
std::map<wxString, wxString> newProperties;
|
|
|
|
|
2022-09-13 16:01:51 +01:00
|
|
|
PAGE_INFO oldPageInfo = brd->GetPageSettings();
|
|
|
|
TITLE_BLOCK oldTitleBlock = brd->GetTitleBlock();
|
|
|
|
|
2015-08-07 18:24:42 +02:00
|
|
|
// Keep also the count of copper layers, to adjust if necessary
|
2017-09-22 17:17:38 +02:00
|
|
|
int initialCopperLayerCount = brd->GetCopperLayerCount();
|
|
|
|
LSET initialEnabledLayers = brd->GetEnabledLayers();
|
2015-08-07 18:24:42 +02:00
|
|
|
|
|
|
|
// Load the data
|
|
|
|
try
|
|
|
|
{
|
2024-08-19 11:47:02 -07:00
|
|
|
std::map<std::string, UTF8> props;
|
2015-08-07 18:24:42 +02:00
|
|
|
|
2023-12-24 01:21:58 +00:00
|
|
|
// PCB_IO_EAGLE can use this info to center the BOARD, but it does not yet.
|
2015-08-07 18:24:42 +02:00
|
|
|
|
2023-04-17 15:39:34 +00:00
|
|
|
props["page_width"] = std::to_string( editFrame->GetPageSizeIU().x );
|
|
|
|
props["page_height"] = std::to_string( editFrame->GetPageSizeIU().y );
|
2015-08-07 18:24:42 +02:00
|
|
|
|
2022-02-11 21:38:31 +00:00
|
|
|
pi.SetQueryUserCallback(
|
|
|
|
[&]( wxString aTitle, int aIcon, wxString aMessage, wxString aAction ) -> bool
|
|
|
|
{
|
|
|
|
KIDIALOG dlg( editFrame, aMessage, aTitle, wxOK | wxCANCEL | aIcon );
|
|
|
|
|
|
|
|
if( !aAction.IsEmpty() )
|
|
|
|
dlg.SetOKLabel( aAction );
|
|
|
|
|
|
|
|
dlg.DoNotShowCheckbox( aMessage, 0 );
|
|
|
|
|
|
|
|
return dlg.ShowModal() == wxID_OK;
|
|
|
|
} );
|
|
|
|
|
2025-06-01 12:43:31 +01:00
|
|
|
WX_PROGRESS_REPORTER progressReporter( editFrame, _( "Load PCB" ), 1, PR_CAN_ABORT );
|
2021-06-23 23:53:08 +01:00
|
|
|
|
2024-07-26 20:49:29 +00:00
|
|
|
editFrame->GetDesignSettings().m_NetSettings->ClearNetclasses();
|
2023-12-27 17:06:23 +00:00
|
|
|
pi.SetProgressReporter( &progressReporter );
|
|
|
|
pi.LoadBoard( fileName, brd, &props, nullptr );
|
2015-08-07 18:24:42 +02:00
|
|
|
}
|
|
|
|
catch( const IO_ERROR& ioe )
|
|
|
|
{
|
2024-12-20 19:28:30 -05:00
|
|
|
DisplayErrorMessage( editFrame, _( "Error loading board." ), ioe.What() );
|
2015-08-07 18:24:42 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-08-19 19:29:09 +01:00
|
|
|
newProperties = brd->GetProperties();
|
|
|
|
|
|
|
|
for( const std::pair<const wxString, wxString>& prop : oldProperties )
|
|
|
|
newProperties[ prop.first ] = prop.second;
|
|
|
|
|
|
|
|
brd->SetProperties( newProperties );
|
|
|
|
|
2022-09-13 16:01:51 +01:00
|
|
|
brd->SetPageSettings( oldPageInfo );
|
|
|
|
brd->SetTitleBlock( oldTitleBlock );
|
|
|
|
|
2017-04-22 09:23:02 +02:00
|
|
|
// rebuild nets and ratsnest before any use of nets
|
2017-09-22 17:17:38 +02:00
|
|
|
brd->BuildListOfNets();
|
2022-11-22 14:24:20 +00:00
|
|
|
brd->SynchronizeNetsAndNetClasses( true );
|
2017-11-11 09:09:24 +01:00
|
|
|
brd->BuildConnectivity();
|
2015-08-07 18:24:42 +02:00
|
|
|
|
|
|
|
// Synchronize layers
|
|
|
|
// we should not ask PLUGINs to do these items:
|
2017-09-22 17:17:38 +02:00
|
|
|
int copperLayerCount = brd->GetCopperLayerCount();
|
2015-08-07 18:24:42 +02:00
|
|
|
|
|
|
|
if( copperLayerCount > initialCopperLayerCount )
|
2017-09-22 17:17:38 +02:00
|
|
|
brd->SetCopperLayerCount( copperLayerCount );
|
2015-08-07 18:24:42 +02:00
|
|
|
|
|
|
|
// Enable all used layers, and make them visible:
|
2017-09-22 17:17:38 +02:00
|
|
|
LSET enabledLayers = brd->GetEnabledLayers();
|
2015-08-07 18:24:42 +02:00
|
|
|
enabledLayers |= initialEnabledLayers;
|
2017-09-22 17:17:38 +02:00
|
|
|
brd->SetEnabledLayers( enabledLayers );
|
|
|
|
brd->SetVisibleLayers( enabledLayers );
|
2017-04-18 20:10:06 +02:00
|
|
|
|
2025-03-20 11:57:59 -04:00
|
|
|
int ret = 0;
|
|
|
|
|
2025-07-13 10:47:21 +01:00
|
|
|
bool placeAsGroup = false;
|
|
|
|
|
|
|
|
if( APP_SETTINGS_BASE* cfg = editFrame->config() )
|
|
|
|
placeAsGroup = cfg->m_DesignBlockChooserPanel.place_as_group;
|
2025-03-20 11:57:59 -04:00
|
|
|
|
2025-07-16 10:40:27 -04:00
|
|
|
if( placeBoardItems( commit, brd, false, false /* Don't reannotate dupes on Append Board */, aSkipMove ) )
|
2025-03-20 11:57:59 -04:00
|
|
|
{
|
|
|
|
if( placeAsGroup )
|
|
|
|
{
|
2025-05-19 21:36:46 +01:00
|
|
|
PCB_GROUP* group = new PCB_GROUP( brd );
|
2025-03-20 11:57:59 -04:00
|
|
|
|
2025-05-08 13:30:31 -04:00
|
|
|
if( aDesignBlock )
|
|
|
|
{
|
|
|
|
group->SetName( aDesignBlock->GetLibId().GetLibItemName() );
|
|
|
|
group->SetDesignBlockLibId( aDesignBlock->GetLibId() );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
group->SetName( wxFileName( fileName ).GetName() );
|
|
|
|
}
|
|
|
|
|
2025-03-20 11:57:59 -04:00
|
|
|
// Get the selection tool selection
|
|
|
|
PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
PCB_SELECTION selection = selTool->GetSelection();
|
|
|
|
|
|
|
|
for( EDA_ITEM* eda_item : selection )
|
|
|
|
{
|
|
|
|
if( eda_item->IsBOARD_ITEM() )
|
|
|
|
{
|
|
|
|
if( static_cast<BOARD_ITEM*>( eda_item )->IsLocked() )
|
|
|
|
group->SetLocked( true );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
commit->Add( group );
|
2025-03-20 11:57:59 -04:00
|
|
|
|
|
|
|
for( EDA_ITEM* eda_item : selection )
|
|
|
|
{
|
|
|
|
if( eda_item->IsBOARD_ITEM() && !static_cast<BOARD_ITEM*>( eda_item )->GetParentFootprint() )
|
2025-05-19 21:36:46 +01:00
|
|
|
{
|
2025-08-19 13:57:07 -04:00
|
|
|
commit->Modify( eda_item );
|
2025-05-19 21:36:46 +01:00
|
|
|
group->AddItem( eda_item );
|
|
|
|
}
|
2025-03-20 11:57:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
selTool->ClearSelection();
|
|
|
|
selTool->select( group );
|
|
|
|
|
|
|
|
m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
|
|
|
|
m_frame->OnModify();
|
|
|
|
m_frame->Refresh();
|
|
|
|
}
|
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
// If we were provided a commit, let the caller control when to push it
|
|
|
|
if( !aCommit )
|
2025-08-20 14:45:06 -04:00
|
|
|
commit->Push( aDesignBlock ? _( "Place Design Block" ) : _( "Append Board" ) );
|
2025-05-19 21:36:46 +01:00
|
|
|
|
2025-04-14 20:13:48 +01:00
|
|
|
editFrame->GetBoard()->BuildConnectivity();
|
2025-03-20 11:57:59 -04:00
|
|
|
ret = 0;
|
|
|
|
}
|
2023-06-27 16:52:50 +01:00
|
|
|
else
|
2025-03-20 11:57:59 -04:00
|
|
|
{
|
2025-08-19 13:57:07 -04:00
|
|
|
// If we were provided a commit, let the caller control when to revert it
|
|
|
|
if( !aCommit )
|
|
|
|
commit->Revert();
|
|
|
|
|
2025-03-20 11:57:59 -04:00
|
|
|
ret = 1;
|
|
|
|
}
|
2023-06-27 16:52:50 +01:00
|
|
|
|
2023-12-31 17:08:09 +00:00
|
|
|
// Refresh the UI for the updated board properties
|
|
|
|
editFrame->GetAppearancePanel()->OnBoardChanged();
|
|
|
|
|
2025-03-20 11:57:59 -04:00
|
|
|
return ret;
|
2015-08-07 18:24:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::Undo( const TOOL_EVENT& aEvent )
|
2019-05-14 20:21:10 +01:00
|
|
|
{
|
|
|
|
PCB_BASE_EDIT_FRAME* editFrame = dynamic_cast<PCB_BASE_EDIT_FRAME*>( m_frame );
|
|
|
|
wxCommandEvent dummy;
|
|
|
|
|
|
|
|
if( editFrame )
|
|
|
|
editFrame->RestoreCopyFromUndoList( dummy );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::Redo( const TOOL_EVENT& aEvent )
|
2019-05-14 20:21:10 +01:00
|
|
|
{
|
|
|
|
PCB_BASE_EDIT_FRAME* editFrame = dynamic_cast<PCB_BASE_EDIT_FRAME*>( m_frame );
|
|
|
|
wxCommandEvent dummy;
|
|
|
|
|
|
|
|
if( editFrame )
|
|
|
|
editFrame->RestoreCopyFromRedoList( dummy );
|
2019-07-16 00:44:01 +01:00
|
|
|
|
2019-05-14 20:21:10 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-08-17 09:25:26 -04:00
|
|
|
int PCB_CONTROL::SnapMode( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
2025-04-12 20:49:19 +01:00
|
|
|
MAGNETIC_SETTINGS& settings = m_isFootprintEditor ? m_frame->GetFootprintEditorSettings()->m_MagneticItems
|
|
|
|
: m_frame->GetPcbNewSettings()->m_MagneticItems;
|
2023-09-16 22:55:29 -04:00
|
|
|
bool& snapMode = settings.allLayers;
|
2023-08-17 09:25:26 -04:00
|
|
|
|
|
|
|
if( aEvent.IsAction( &PCB_ACTIONS::magneticSnapActiveLayer ) )
|
|
|
|
snapMode = false;
|
|
|
|
else if( aEvent.IsAction( &PCB_ACTIONS::magneticSnapAllLayers ) )
|
|
|
|
snapMode = true;
|
|
|
|
else
|
|
|
|
snapMode = !snapMode;
|
|
|
|
|
2024-12-30 18:58:27 +00:00
|
|
|
m_toolMgr->PostEvent( PCB_EVENTS::SnappingModeChangedByKeyEvent() );
|
2023-08-17 09:25:26 -04:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PCB_CONTROL::SnapModeFeedback( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
if( !Pgm().GetCommonSettings()->m_Input.hotkey_feedback )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
wxArrayString labels;
|
|
|
|
labels.Add( _( "Active Layer" ) );
|
|
|
|
labels.Add( _( "All Layers" ) );
|
|
|
|
|
|
|
|
if( !m_frame->GetHotkeyPopup() )
|
|
|
|
m_frame->CreateHotkeyPopup();
|
|
|
|
|
|
|
|
HOTKEY_CYCLE_POPUP* popup = m_frame->GetHotkeyPopup();
|
|
|
|
|
2025-04-12 20:49:19 +01:00
|
|
|
MAGNETIC_SETTINGS& settings = m_isFootprintEditor ? m_frame->GetFootprintEditorSettings()->m_MagneticItems
|
|
|
|
: m_frame->GetPcbNewSettings()->m_MagneticItems;
|
2023-09-16 22:55:29 -04:00
|
|
|
|
2023-08-17 09:25:26 -04:00
|
|
|
if( popup )
|
2023-09-16 22:55:29 -04:00
|
|
|
popup->Popup( _( "Object Snapping" ), labels, static_cast<int>( settings.allLayers ) );
|
2023-08-17 09:25:26 -04:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
int PCB_CONTROL::UpdateMessagePanel( const TOOL_EVENT& aEvent )
|
2019-05-29 00:23:58 +01:00
|
|
|
{
|
2022-07-15 16:14:11 +01:00
|
|
|
PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
ROUTER_TOOL* routerTool = m_toolMgr->GetTool<ROUTER_TOOL>();
|
|
|
|
PCB_SELECTION& selection = selTool->GetSelection();
|
|
|
|
PCB_EDIT_FRAME* pcbFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
|
|
|
|
std::shared_ptr<DRC_ENGINE> drcEngine = m_frame->GetBoard()->GetDesignSettings().m_DRCEngine;
|
|
|
|
DRC_CONSTRAINT constraint;
|
2022-07-13 15:04:32 -07:00
|
|
|
|
|
|
|
std::vector<MSG_PANEL_ITEM> msgItems;
|
2019-05-29 00:23:58 +01:00
|
|
|
|
2022-02-20 16:56:53 +00:00
|
|
|
if( routerTool && routerTool->RoutingInProgress() )
|
2022-02-20 13:53:34 +00:00
|
|
|
{
|
|
|
|
routerTool->UpdateMessagePanel();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-07-26 18:44:29 +01:00
|
|
|
if( !pcbFrame && !m_frame->GetModel() )
|
2022-07-22 23:05:25 +01:00
|
|
|
return 0;
|
|
|
|
|
2022-07-13 15:04:32 -07:00
|
|
|
if( selection.Empty() )
|
|
|
|
{
|
2022-07-26 18:44:29 +01:00
|
|
|
if( !pcbFrame )
|
2022-07-13 15:04:32 -07:00
|
|
|
{
|
2022-07-26 18:44:29 +01:00
|
|
|
FOOTPRINT* fp = static_cast<FOOTPRINT*>( m_frame->GetModel() );
|
2023-01-25 17:46:49 +01:00
|
|
|
fp->GetMsgPanelInfo( m_frame, msgItems );
|
2022-07-13 15:04:32 -07:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_frame->SetMsgPanel( m_frame->GetBoard() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if( selection.GetSize() == 1 )
|
2019-05-29 00:23:58 +01:00
|
|
|
{
|
2023-10-15 22:45:18 +01:00
|
|
|
EDA_ITEM* item = selection.Front();
|
2019-06-03 23:45:06 +01:00
|
|
|
|
2025-01-07 19:58:40 +08:00
|
|
|
if( std::optional<wxString> uuid = GetMsgPanelDisplayUuid( item->m_Uuid ) )
|
|
|
|
msgItems.emplace_back( _( "UUID" ), *uuid );
|
|
|
|
|
2020-04-24 14:36:10 +01:00
|
|
|
item->GetMsgPanelInfo( m_frame, msgItems );
|
2023-10-15 22:45:18 +01:00
|
|
|
|
|
|
|
PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item );
|
|
|
|
NETINFO_ITEM* net = track ? track->GetNet() : nullptr;
|
|
|
|
NETINFO_ITEM* coupledNet = net ? m_frame->GetBoard()->DpCoupledNet( net ) : nullptr;
|
|
|
|
|
|
|
|
if( coupledNet )
|
|
|
|
{
|
|
|
|
SEG trackSeg( track->GetStart(), track->GetEnd() );
|
|
|
|
PCB_TRACK* coupledItem = nullptr;
|
2023-10-23 22:18:45 +03:00
|
|
|
SEG::ecoord closestDist_sq = VECTOR2I::ECOORD_MAX;
|
2023-10-15 22:45:18 +01:00
|
|
|
|
|
|
|
for( PCB_TRACK* candidate : m_frame->GetBoard()->Tracks() )
|
|
|
|
{
|
|
|
|
if( candidate->GetNet() != coupledNet )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
SEG::ecoord dist_sq = trackSeg.SquaredDistance( SEG( candidate->GetStart(),
|
|
|
|
candidate->GetEnd() ) );
|
|
|
|
|
|
|
|
if( !coupledItem || dist_sq < closestDist_sq )
|
|
|
|
{
|
|
|
|
coupledItem = candidate;
|
|
|
|
closestDist_sq = dist_sq;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
constraint = drcEngine->EvalRules( DIFF_PAIR_GAP_CONSTRAINT, track, coupledItem,
|
|
|
|
track->GetLayer() );
|
|
|
|
|
|
|
|
wxString msg = m_frame->MessageTextFromMinOptMax( constraint.Value() );
|
|
|
|
|
|
|
|
if( !msg.IsEmpty() )
|
|
|
|
{
|
|
|
|
msgItems.emplace_back( wxString::Format( _( "DP Gap Constraints: %s" ), msg ),
|
|
|
|
wxString::Format( _( "(from %s)" ), constraint.GetName() ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
constraint = drcEngine->EvalRules( MAX_UNCOUPLED_CONSTRAINT, track,
|
|
|
|
coupledItem, track->GetLayer() );
|
|
|
|
|
|
|
|
if( constraint.Value().HasMax() )
|
|
|
|
{
|
|
|
|
msg = m_frame->MessageTextFromValue( constraint.Value().Max() );
|
|
|
|
msgItems.emplace_back( wxString::Format( _( "DP Max Uncoupled-length: %s" ), msg ),
|
|
|
|
wxString::Format( _( "(from %s)" ), constraint.GetName() ) );
|
|
|
|
}
|
|
|
|
}
|
2019-05-29 00:23:58 +01:00
|
|
|
}
|
2022-07-22 23:05:25 +01:00
|
|
|
else if( pcbFrame && selection.GetSize() == 2 )
|
2019-06-03 23:45:06 +01:00
|
|
|
{
|
2022-07-22 23:05:25 +01:00
|
|
|
// Pair selection broken into multiple, optional data, starting with the selected item
|
|
|
|
// names
|
2022-07-15 16:14:11 +01:00
|
|
|
|
2025-06-08 23:13:58 +01:00
|
|
|
BOARD_ITEM* a = dynamic_cast<BOARD_ITEM*>( selection[0] );
|
|
|
|
BOARD_ITEM* b = dynamic_cast<BOARD_ITEM*>( selection[1] );
|
2019-06-03 23:45:06 +01:00
|
|
|
|
2025-06-14 19:39:30 +01:00
|
|
|
if( a && b )
|
|
|
|
{
|
|
|
|
msgItems.emplace_back( MSG_PANEL_ITEM( a->GetItemDescription( m_frame, false ),
|
|
|
|
b->GetItemDescription( m_frame, false ) ) );
|
|
|
|
}
|
2019-09-04 17:18:42 +01:00
|
|
|
|
2025-06-08 23:13:58 +01:00
|
|
|
BOARD_CONNECTED_ITEM* a_conn = dynamic_cast<BOARD_CONNECTED_ITEM*>( a );
|
|
|
|
BOARD_CONNECTED_ITEM* b_conn = dynamic_cast<BOARD_CONNECTED_ITEM*>( b );
|
2019-09-04 17:18:42 +01:00
|
|
|
|
2022-07-15 16:14:11 +01:00
|
|
|
if( a_conn && b_conn )
|
2022-07-13 15:04:32 -07:00
|
|
|
{
|
2022-07-15 16:14:11 +01:00
|
|
|
LSET overlap = a_conn->GetLayerSet() & b_conn->GetLayerSet() & LSET::AllCuMask();
|
|
|
|
int a_netcode = a_conn->GetNetCode();
|
|
|
|
int b_netcode = b_conn->GetNetCode();
|
2019-09-04 17:18:42 +01:00
|
|
|
|
2025-07-07 17:39:22 +01:00
|
|
|
if( overlap.count() > 0 )
|
2022-07-15 16:14:11 +01:00
|
|
|
{
|
2022-07-29 22:02:35 +01:00
|
|
|
PCB_LAYER_ID layer = overlap.CuStack().front();
|
|
|
|
|
2025-07-07 17:39:22 +01:00
|
|
|
if( a_netcode != b_netcode || a_netcode < 0 || b_netcode < 0 )
|
|
|
|
{
|
|
|
|
constraint = drcEngine->EvalRules( CLEARANCE_CONSTRAINT, a, b, layer );
|
|
|
|
msgItems.emplace_back( _( "Resolved Clearance" ),
|
|
|
|
m_frame->MessageTextFromValue( constraint.m_Value.Min() ) );
|
|
|
|
}
|
2022-07-21 15:01:50 -07:00
|
|
|
|
2024-03-11 16:28:51 -07:00
|
|
|
std::shared_ptr<SHAPE> a_shape( a_conn->GetEffectiveShape( layer ) );
|
|
|
|
std::shared_ptr<SHAPE> b_shape( b_conn->GetEffectiveShape( layer ) );
|
2024-03-08 10:30:15 -08:00
|
|
|
|
2024-03-11 16:28:51 -07:00
|
|
|
int actual_clearance = a_shape->GetClearance( b_shape.get() );
|
2024-03-08 10:30:15 -08:00
|
|
|
|
2024-03-11 16:28:51 -07:00
|
|
|
if( actual_clearance > -1 && actual_clearance < std::numeric_limits<int>::max() )
|
|
|
|
{
|
2025-03-27 10:13:31 +00:00
|
|
|
msgItems.emplace_back( _( "Actual Clearance" ),
|
|
|
|
m_frame->MessageTextFromValue( actual_clearance ) );
|
2022-07-21 15:01:50 -07:00
|
|
|
}
|
2022-07-15 16:14:11 +01:00
|
|
|
}
|
2022-07-13 15:04:32 -07:00
|
|
|
}
|
|
|
|
|
2025-06-15 21:52:52 +01:00
|
|
|
if( a && b && ( a->HasHole() || b->HasHole() ) )
|
2022-07-15 16:14:11 +01:00
|
|
|
{
|
|
|
|
PCB_LAYER_ID active = m_frame->GetActiveLayer();
|
|
|
|
PCB_LAYER_ID layer = UNDEFINED_LAYER;
|
|
|
|
|
|
|
|
if( b->IsOnLayer( active ) && IsCopperLayer( active ) )
|
|
|
|
layer = active;
|
|
|
|
else if( b->HasHole() && a->IsOnLayer( active ) && IsCopperLayer( active ) )
|
|
|
|
layer = active;
|
|
|
|
else if( a->HasHole() && b->IsOnCopperLayer() )
|
|
|
|
layer = b->GetLayer();
|
2022-09-20 13:40:45 -07:00
|
|
|
else if( b->HasHole() && a->IsOnCopperLayer() )
|
2022-07-15 16:14:11 +01:00
|
|
|
layer = a->GetLayer();
|
|
|
|
|
2022-09-20 13:40:45 -07:00
|
|
|
if( IsCopperLayer( layer ) )
|
2022-07-15 16:14:11 +01:00
|
|
|
{
|
2022-07-22 23:05:25 +01:00
|
|
|
int actual = std::numeric_limits<int>::max();
|
|
|
|
|
2022-09-20 13:40:45 -07:00
|
|
|
if( a->HasHole() && b->IsOnCopperLayer() )
|
2022-07-22 23:05:25 +01:00
|
|
|
{
|
|
|
|
std::shared_ptr<SHAPE_SEGMENT> hole = a->GetEffectiveHoleShape();
|
|
|
|
std::shared_ptr<SHAPE> other( b->GetEffectiveShape( layer ) );
|
|
|
|
|
|
|
|
actual = std::min( actual, hole->GetClearance( other.get() ) );
|
|
|
|
}
|
|
|
|
|
2022-09-20 13:40:45 -07:00
|
|
|
if( b->HasHole() && a->IsOnCopperLayer() )
|
2022-07-22 23:05:25 +01:00
|
|
|
{
|
|
|
|
std::shared_ptr<SHAPE_SEGMENT> hole = b->GetEffectiveHoleShape();
|
|
|
|
std::shared_ptr<SHAPE> other( a->GetEffectiveShape( layer ) );
|
|
|
|
|
|
|
|
actual = std::min( actual, hole->GetClearance( other.get() ) );
|
|
|
|
}
|
|
|
|
|
2022-09-20 13:40:45 -07:00
|
|
|
if( actual < std::numeric_limits<int>::max() )
|
2022-07-22 23:05:25 +01:00
|
|
|
{
|
2022-09-20 13:40:45 -07:00
|
|
|
constraint = drcEngine->EvalRules( HOLE_CLEARANCE_CONSTRAINT, a, b, layer );
|
2025-03-27 10:13:31 +00:00
|
|
|
msgItems.emplace_back( _( "Resolved Hole Clearance" ),
|
|
|
|
m_frame->MessageTextFromValue( constraint.m_Value.Min() ) );
|
2022-09-20 13:40:45 -07:00
|
|
|
|
|
|
|
if( actual > -1 && actual < std::numeric_limits<int>::max() )
|
|
|
|
{
|
2025-03-27 10:13:31 +00:00
|
|
|
msgItems.emplace_back( _( "Actual Hole Clearance" ),
|
|
|
|
m_frame->MessageTextFromValue( actual ) );
|
2022-09-20 13:40:45 -07:00
|
|
|
}
|
2022-07-22 23:05:25 +01:00
|
|
|
}
|
2022-07-15 16:14:11 +01:00
|
|
|
}
|
|
|
|
}
|
2022-07-13 15:04:32 -07:00
|
|
|
|
2025-06-23 10:28:23 -06:00
|
|
|
if( a && b )
|
2022-07-13 15:04:32 -07:00
|
|
|
{
|
2025-06-23 10:28:23 -06:00
|
|
|
for( PCB_LAYER_ID edgeLayer : { Edge_Cuts, Margin } )
|
2022-07-15 16:14:11 +01:00
|
|
|
{
|
2025-06-23 10:28:23 -06:00
|
|
|
PCB_LAYER_ID active = m_frame->GetActiveLayer();
|
|
|
|
PCB_LAYER_ID layer = UNDEFINED_LAYER;
|
2022-07-13 15:04:32 -07:00
|
|
|
|
2025-06-23 10:28:23 -06:00
|
|
|
if( a->IsOnLayer( edgeLayer ) && b->Type() != PCB_FOOTPRINT_T )
|
2022-07-15 16:14:11 +01:00
|
|
|
{
|
2025-06-23 10:28:23 -06:00
|
|
|
if( b->IsOnLayer( active ) && IsCopperLayer( active ) )
|
|
|
|
layer = active;
|
|
|
|
else if( IsCopperLayer( b->GetLayer() ) )
|
|
|
|
layer = b->GetLayer();
|
2022-07-15 16:14:11 +01:00
|
|
|
}
|
2025-06-23 10:28:23 -06:00
|
|
|
else if( b->IsOnLayer( edgeLayer ) && a->Type() != PCB_FOOTPRINT_T )
|
2022-07-15 16:14:11 +01:00
|
|
|
{
|
2025-06-23 10:28:23 -06:00
|
|
|
if( a->IsOnLayer( active ) && IsCopperLayer( active ) )
|
|
|
|
layer = active;
|
|
|
|
else if( IsCopperLayer( a->GetLayer() ) )
|
|
|
|
layer = a->GetLayer();
|
|
|
|
}
|
|
|
|
|
|
|
|
if( layer >= 0 )
|
|
|
|
{
|
|
|
|
constraint = drcEngine->EvalRules( EDGE_CLEARANCE_CONSTRAINT, a, b, layer );
|
|
|
|
|
|
|
|
if( edgeLayer == Edge_Cuts )
|
|
|
|
{
|
|
|
|
msgItems.emplace_back( _( "Resolved Edge Clearance" ),
|
|
|
|
m_frame->MessageTextFromValue( constraint.m_Value.Min() ) );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
msgItems.emplace_back( _( "Resolved Margin Clearance" ),
|
|
|
|
m_frame->MessageTextFromValue( constraint.m_Value.Min() ) );
|
|
|
|
}
|
2022-07-15 16:14:11 +01:00
|
|
|
}
|
|
|
|
}
|
2022-07-13 15:04:32 -07:00
|
|
|
}
|
2019-05-29 00:23:58 +01:00
|
|
|
}
|
|
|
|
|
2025-01-06 18:44:59 +05:00
|
|
|
if( selection.GetSize() )
|
2022-07-15 16:14:11 +01:00
|
|
|
{
|
2025-01-06 18:44:59 +05:00
|
|
|
if( msgItems.empty() )
|
2022-08-05 13:49:01 +02:00
|
|
|
{
|
|
|
|
msgItems.emplace_back( _( "Selected Items" ),
|
|
|
|
wxString::Format( wxT( "%d" ), selection.GetSize() ) );
|
2022-11-22 14:53:24 +00:00
|
|
|
|
2024-01-21 22:49:29 +00:00
|
|
|
if( m_isBoardEditor )
|
2022-11-22 14:53:24 +00:00
|
|
|
{
|
2024-01-21 22:49:29 +00:00
|
|
|
std::set<wxString> netNames;
|
|
|
|
std::set<wxString> netClasses;
|
|
|
|
|
|
|
|
for( EDA_ITEM* item : selection )
|
2022-11-22 14:53:24 +00:00
|
|
|
{
|
2024-01-21 22:49:29 +00:00
|
|
|
if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
|
|
|
|
{
|
|
|
|
netNames.insert( UnescapeString( bci->GetNetname() ) );
|
2025-03-27 10:13:31 +00:00
|
|
|
netClasses.insert( UnescapeString( bci->GetEffectiveNetClass()->GetHumanReadableName() ) );
|
2022-11-22 14:53:24 +00:00
|
|
|
|
2024-01-21 22:49:29 +00:00
|
|
|
if( netNames.size() > 1 && netClasses.size() > 1 )
|
|
|
|
break;
|
|
|
|
}
|
2022-11-22 14:53:24 +00:00
|
|
|
}
|
|
|
|
|
2024-01-21 22:49:29 +00:00
|
|
|
if( netNames.size() == 1 )
|
|
|
|
msgItems.emplace_back( _( "Net" ), *netNames.begin() );
|
2022-11-22 14:53:24 +00:00
|
|
|
|
2024-01-21 22:49:29 +00:00
|
|
|
if( netClasses.size() == 1 )
|
|
|
|
msgItems.emplace_back( _( "Resolved Netclass" ), *netClasses.begin() );
|
|
|
|
}
|
2022-08-05 13:49:01 +02:00
|
|
|
}
|
2025-01-06 18:44:59 +05:00
|
|
|
|
|
|
|
if( selection.GetSize() >= 2 )
|
2022-08-05 13:49:01 +02:00
|
|
|
{
|
2025-01-06 18:44:59 +05:00
|
|
|
bool lengthValid = true;
|
|
|
|
double selectedLength = 0;
|
|
|
|
|
|
|
|
// Lambda to accumulate track length if item is a track or arc, otherwise mark invalid
|
|
|
|
std::function<void( EDA_ITEM* )> accumulateTrackLength;
|
|
|
|
|
2025-03-26 17:05:38 +00:00
|
|
|
accumulateTrackLength =
|
|
|
|
[&]( EDA_ITEM* aItem )
|
2025-01-07 15:33:39 +08:00
|
|
|
{
|
2025-03-27 10:13:31 +00:00
|
|
|
if( aItem->Type() == PCB_TRACE_T || aItem->Type() == PCB_ARC_T )
|
|
|
|
{
|
|
|
|
selectedLength += static_cast<PCB_TRACK*>( aItem )->GetLength();
|
|
|
|
}
|
|
|
|
else if( aItem->Type() == PCB_VIA_T )
|
2025-03-26 17:05:38 +00:00
|
|
|
{
|
2025-03-27 10:13:31 +00:00
|
|
|
// zero 2D length
|
2025-03-26 17:05:38 +00:00
|
|
|
}
|
2025-03-27 10:13:31 +00:00
|
|
|
else if( aItem->Type() == PCB_SHAPE_T )
|
2025-03-26 17:05:38 +00:00
|
|
|
{
|
2025-03-27 10:13:31 +00:00
|
|
|
PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
|
2025-03-26 17:05:38 +00:00
|
|
|
|
2025-03-27 10:13:31 +00:00
|
|
|
if( shape->GetShape() == SHAPE_T::SEGMENT
|
|
|
|
|| shape->GetShape() == SHAPE_T::ARC
|
|
|
|
|| shape->GetShape() == SHAPE_T::BEZIER )
|
2025-03-26 17:05:38 +00:00
|
|
|
{
|
|
|
|
selectedLength += shape->GetLength();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
lengthValid = false;
|
|
|
|
}
|
|
|
|
}
|
2025-03-27 10:13:31 +00:00
|
|
|
// Use dynamic_cast to include PCB_GENERATORs.
|
2025-03-26 17:05:38 +00:00
|
|
|
else if( PCB_GROUP* group = dynamic_cast<PCB_GROUP*>( aItem ) )
|
|
|
|
{
|
2025-05-19 21:36:46 +01:00
|
|
|
group->RunOnChildren( accumulateTrackLength, RECURSE_MODE::RECURSE );
|
2025-03-26 17:05:38 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
lengthValid = false;
|
|
|
|
}
|
|
|
|
};
|
2025-01-06 18:44:59 +05:00
|
|
|
|
|
|
|
for( EDA_ITEM* item : selection )
|
2025-03-27 10:13:31 +00:00
|
|
|
{
|
|
|
|
if( lengthValid )
|
|
|
|
accumulateTrackLength( item );
|
|
|
|
}
|
2025-01-06 18:44:59 +05:00
|
|
|
|
|
|
|
if( lengthValid )
|
|
|
|
{
|
|
|
|
msgItems.emplace_back( _( "Selected 2D Length" ),
|
|
|
|
m_frame->MessageTextFromValue( selectedLength ) );
|
|
|
|
}
|
2022-08-05 13:49:01 +02:00
|
|
|
}
|
2025-03-27 10:13:31 +00:00
|
|
|
|
|
|
|
if( selection.GetSize() >= 2 && selection.GetSize() < 100 )
|
|
|
|
{
|
|
|
|
LSET enabledCopper = LSET::AllCuMask( m_frame->GetBoard()->GetCopperLayerCount() );
|
|
|
|
bool areaValid = true;
|
|
|
|
|
|
|
|
std::map<PCB_LAYER_ID, SHAPE_POLY_SET> copperPolys;
|
|
|
|
SHAPE_POLY_SET holes;
|
|
|
|
|
|
|
|
std::function<void( EDA_ITEM* )> accumulateArea;
|
|
|
|
|
|
|
|
accumulateArea =
|
|
|
|
[&]( EDA_ITEM* aItem )
|
|
|
|
{
|
2025-05-07 13:07:21 -07:00
|
|
|
if( aItem->Type() == PCB_FOOTPRINT_T || aItem->Type() == PCB_MARKER_T )
|
2025-03-27 10:13:31 +00:00
|
|
|
{
|
|
|
|
areaValid = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-05-19 21:36:46 +01:00
|
|
|
if( PCB_GROUP* group = dynamic_cast<PCB_GROUP*>( aItem ) )
|
2025-05-06 09:45:07 -04:00
|
|
|
{
|
2025-05-19 21:36:46 +01:00
|
|
|
group->RunOnChildren( accumulateArea, RECURSE_MODE::RECURSE );
|
2025-05-06 09:45:07 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-27 10:13:31 +00:00
|
|
|
if( BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( aItem ) )
|
|
|
|
{
|
2025-03-27 10:43:44 -04:00
|
|
|
boardItem->RunOnChildren( accumulateArea, RECURSE_MODE::NO_RECURSE );
|
2025-03-27 10:13:31 +00:00
|
|
|
|
|
|
|
for( PCB_LAYER_ID layer : LSET( boardItem->GetLayerSet() & enabledCopper ) )
|
|
|
|
{
|
|
|
|
boardItem->TransformShapeToPolySet( copperPolys[layer], layer, 0,
|
|
|
|
ARC_LOW_DEF, ERROR_INSIDE );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( aItem->Type() == PCB_PAD_T && static_cast<PAD*>( aItem )->HasHole() )
|
|
|
|
{
|
|
|
|
static_cast<PAD*>( aItem )->TransformHoleToPolygon( holes, 0, ARC_LOW_DEF,
|
|
|
|
ERROR_OUTSIDE );
|
|
|
|
}
|
|
|
|
else if( aItem->Type() == PCB_VIA_T )
|
|
|
|
{
|
|
|
|
PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
|
|
|
|
VECTOR2I center = via->GetPosition();
|
|
|
|
int R = via->GetDrillValue() / 2;
|
|
|
|
|
|
|
|
TransformCircleToPolygon( holes, center, R, ARC_LOW_DEF, ERROR_OUTSIDE );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
for( EDA_ITEM* item : selection )
|
|
|
|
{
|
|
|
|
if( areaValid )
|
|
|
|
accumulateArea( item );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( areaValid )
|
|
|
|
{
|
|
|
|
double area = 0.0;
|
|
|
|
|
|
|
|
for( auto& [layer, copperPoly] : copperPolys )
|
|
|
|
{
|
|
|
|
copperPoly.BooleanSubtract( holes );
|
|
|
|
area += copperPoly.Area();
|
|
|
|
}
|
|
|
|
|
|
|
|
msgItems.emplace_back( _( "Selected 2D Copper Area" ),
|
|
|
|
m_frame->MessageTextFromValue( area, true, EDA_DATA_TYPE::AREA ) );
|
|
|
|
}
|
|
|
|
}
|
2022-07-15 16:14:11 +01:00
|
|
|
}
|
2025-01-06 18:44:59 +05:00
|
|
|
else
|
|
|
|
{
|
|
|
|
m_frame->GetBoard()->GetMsgPanelInfo( m_frame, msgItems );
|
|
|
|
}
|
2022-07-13 15:04:32 -07:00
|
|
|
|
|
|
|
m_frame->SetMsgPanel( msgItems );
|
|
|
|
|
2019-05-29 00:23:58 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-09-14 22:28:09 +00:00
|
|
|
int PCB_CONTROL::DdAppendBoard( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
wxFileName fileName = wxFileName( *aEvent.Parameter<wxString*>() );
|
|
|
|
|
|
|
|
PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
|
|
|
|
|
|
|
|
if( !editFrame )
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
wxString filePath = fileName.GetFullPath();
|
2023-12-19 17:39:26 +00:00
|
|
|
PCB_IO_MGR::PCB_FILE_T pluginType = PCB_IO_MGR::FindPluginTypeFromBoardPath( filePath );
|
2023-12-27 20:39:29 +00:00
|
|
|
IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( pluginType ) );
|
2022-09-14 22:28:09 +00:00
|
|
|
|
2023-08-26 22:28:53 +03:00
|
|
|
if( !pi )
|
|
|
|
return 1;
|
|
|
|
|
2022-09-14 22:28:09 +00:00
|
|
|
return AppendBoard( *pi, filePath );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-04-12 14:39:00 +01:00
|
|
|
int PCB_CONTROL::PlaceCharacteristics( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
BOARD_COMMIT commit( this );
|
|
|
|
BOARD_DESIGN_SETTINGS& settings = m_frame->GetBoard()->GetDesignSettings();
|
|
|
|
BOARD_STACKUP& stackup = settings.GetStackupDescriptor();
|
|
|
|
|
|
|
|
stackup.SynchronizeWithBoard( &settings );
|
|
|
|
|
|
|
|
PCB_TABLE* table = new PCB_TABLE( m_frame->GetModel(), pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ) );
|
|
|
|
table->SetLayer( m_frame->GetActiveLayer() );
|
|
|
|
table->SetColCount( 4 );
|
|
|
|
|
|
|
|
auto addHeaderCell =
|
|
|
|
[&]( const wxString& text )
|
|
|
|
{
|
|
|
|
PCB_TABLECELL* c = new PCB_TABLECELL( table );
|
|
|
|
c->SetTextSize( VECTOR2I( pcbIUScale.mmToIU( 2.0 ), pcbIUScale.mmToIU( 2.0 ) ) );
|
|
|
|
c->SetTextThickness( pcbIUScale.mmToIU( 0.4 ) );
|
|
|
|
c->SetText( text );
|
|
|
|
c->SetColSpan( table->GetColCount() );
|
|
|
|
table->AddCell( c );
|
|
|
|
};
|
|
|
|
|
|
|
|
auto addDataCell =
|
|
|
|
[&]( const wxString& text )
|
|
|
|
{
|
|
|
|
PCB_TABLECELL* c = new PCB_TABLECELL( table );
|
|
|
|
c->SetTextSize( VECTOR2I( pcbIUScale.mmToIU( 1.5 ), pcbIUScale.mmToIU( 1.5 ) ) );
|
|
|
|
c->SetTextThickness( pcbIUScale.mmToIU( 0.2 ) );
|
|
|
|
c->SetText( text );
|
|
|
|
table->AddCell( c );
|
|
|
|
};
|
|
|
|
|
|
|
|
addHeaderCell( _( "BOARD CHARACTERISTICS" ) );
|
|
|
|
|
|
|
|
for( int col = 1; col < table->GetColCount(); ++col )
|
|
|
|
{
|
|
|
|
addHeaderCell( wxEmptyString );
|
|
|
|
table->GetCell( 0, col )->SetColSpan( 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
addDataCell( _( "Copper layer count: " ) );
|
|
|
|
addDataCell( EDA_UNIT_UTILS::UI::StringFromValue( unityScale, EDA_UNITS::UNSCALED,
|
|
|
|
settings.GetCopperLayerCount(), false ) );
|
|
|
|
|
|
|
|
addDataCell( _( "Board thickness: " ) );
|
|
|
|
addDataCell( m_frame->MessageTextFromValue( settings.GetBoardThickness(), true ) );
|
|
|
|
|
|
|
|
SHAPE_POLY_SET outline;
|
|
|
|
m_frame->GetBoard()->GetBoardPolygonOutlines( outline );
|
|
|
|
BOX2I size = outline.BBox();
|
|
|
|
|
|
|
|
addDataCell( _( "Board overall dimensions: " ) );
|
|
|
|
addDataCell( wxString::Format( wxT( "%s x %s" ),
|
|
|
|
m_frame->MessageTextFromValue( size.GetWidth(), true ),
|
|
|
|
m_frame->MessageTextFromValue( size.GetHeight(), true ) ) );
|
|
|
|
|
|
|
|
addDataCell( wxEmptyString );
|
|
|
|
addDataCell( wxEmptyString );
|
|
|
|
|
|
|
|
addDataCell( _( "Min track/spacing: " ) );
|
|
|
|
addDataCell( wxString::Format( wxT( "%s / %s" ),
|
|
|
|
m_frame->MessageTextFromValue( settings.m_TrackMinWidth, true ),
|
|
|
|
m_frame->MessageTextFromValue( settings.m_MinClearance, true ) ) );
|
|
|
|
|
|
|
|
double holeSize = std::min( settings.m_MinThroughDrill, settings.m_ViasMinSize );
|
|
|
|
|
|
|
|
addDataCell( _( "Min hole diameter: " ) );
|
|
|
|
addDataCell( m_frame->MessageTextFromValue( holeSize, true ) );
|
|
|
|
|
|
|
|
addDataCell( _( "Copper finish: " ) );
|
|
|
|
addDataCell( stackup.m_FinishType );
|
|
|
|
|
|
|
|
addDataCell( _( "Impedance control: " ) );
|
|
|
|
addDataCell( stackup.m_HasDielectricConstrains ? _( "Yes" ) : _( "No" ) );
|
|
|
|
|
|
|
|
addDataCell( _( "Castellated pads: " ) );
|
2025-08-11 18:30:17 +02:00
|
|
|
int castellated_pad_count = m_frame->GetBoard()->GetPadWithCastellatedAttrCount();
|
|
|
|
addDataCell( castellated_pad_count ? _( "Yes" ) : _( "No" ) );
|
|
|
|
|
|
|
|
addDataCell( _( "Press-fit pads: " ) );
|
|
|
|
int pressfit_pad_count = m_frame->GetBoard()->GetPadWithPressFitAttrCount();
|
|
|
|
addDataCell( pressfit_pad_count ? _( "Yes" ) : _( "No" ) );
|
2025-04-12 14:39:00 +01:00
|
|
|
|
|
|
|
addDataCell( _( "Plated board edge: " ) );
|
|
|
|
addDataCell( stackup.m_EdgePlating ? _( "Yes" ) : _( "No" ) );
|
|
|
|
|
|
|
|
wxString msg;
|
|
|
|
|
|
|
|
switch( stackup.m_EdgeConnectorConstraints )
|
|
|
|
{
|
|
|
|
case BS_EDGE_CONNECTOR_NONE: msg = _( "No" ); break;
|
|
|
|
case BS_EDGE_CONNECTOR_IN_USE: msg = _( "Yes" ); break;
|
|
|
|
case BS_EDGE_CONNECTOR_BEVELLED: msg = _( "Yes, Bevelled" ); break;
|
|
|
|
}
|
|
|
|
|
|
|
|
addDataCell( _( "Edge card connectors: " ) );
|
|
|
|
addDataCell( msg );
|
|
|
|
|
2025-08-11 18:30:17 +02:00
|
|
|
// We are building a table having 4 columns.
|
|
|
|
// So we must have a cell count multible of 4, to have fully build row.
|
|
|
|
// Othewise the table is really badly drawn.
|
|
|
|
std::vector<PCB_TABLECELL*> cells_list = table->GetCells();
|
|
|
|
int cell_to_add_cnt = cells_list.size() % table->GetColCount();
|
|
|
|
|
|
|
|
for( int ii = 0; ii < cell_to_add_cnt; ii++ )
|
|
|
|
addDataCell( wxEmptyString );
|
2025-04-12 14:39:00 +01:00
|
|
|
|
|
|
|
table->SetStrokeExternal( false );
|
|
|
|
table->SetStrokeHeaderSeparator( false );
|
|
|
|
table->SetStrokeColumns( false );
|
|
|
|
table->SetStrokeRows( false );
|
|
|
|
table->Autosize();
|
|
|
|
|
|
|
|
std::vector<BOARD_ITEM*> items;
|
|
|
|
items.push_back( table );
|
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
if( placeBoardItems( &commit, items, true, true, false, false ) )
|
2025-04-12 14:39:00 +01:00
|
|
|
commit.Push( _( "Place Board Characteristics" ) );
|
|
|
|
else
|
|
|
|
delete table;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int PCB_CONTROL::PlaceStackup( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
BOARD_COMMIT commit( this );
|
|
|
|
BOARD_DESIGN_SETTINGS& settings = m_frame->GetBoard()->GetDesignSettings();
|
|
|
|
BOARD_STACKUP& stackup = settings.GetStackupDescriptor();
|
|
|
|
|
|
|
|
stackup.SynchronizeWithBoard( &settings );
|
|
|
|
|
|
|
|
std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
|
|
|
|
|
|
|
|
PCB_TABLE* table = new PCB_TABLE( m_frame->GetModel(), pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ) );
|
|
|
|
table->SetLayer( m_frame->GetActiveLayer() );
|
|
|
|
table->SetColCount( 7 );
|
|
|
|
|
2025-08-07 02:38:07 +08:00
|
|
|
const auto addHeaderCell =
|
2025-04-12 14:39:00 +01:00
|
|
|
[&]( const wxString& text )
|
|
|
|
{
|
|
|
|
PCB_TABLECELL* c = new PCB_TABLECELL( table );
|
|
|
|
c->SetTextSize( VECTOR2I( pcbIUScale.mmToIU( 1.5 ), pcbIUScale.mmToIU( 1.5 ) ) );
|
|
|
|
c->SetTextThickness( pcbIUScale.mmToIU( 0.3 ) );
|
|
|
|
c->SetText( text );
|
|
|
|
table->AddCell( c );
|
|
|
|
};
|
|
|
|
|
2025-08-07 02:38:07 +08:00
|
|
|
const auto addDataCell =
|
2025-04-12 14:39:00 +01:00
|
|
|
[&]( const wxString& text, const char align = 'L' )
|
|
|
|
{
|
|
|
|
PCB_TABLECELL* c = new PCB_TABLECELL( table );
|
|
|
|
c->SetTextSize( VECTOR2I( pcbIUScale.mmToIU( 1.5 ), pcbIUScale.mmToIU( 1.5 ) ) );
|
|
|
|
c->SetTextThickness( pcbIUScale.mmToIU( 0.2 ) );
|
|
|
|
|
|
|
|
if( align == 'R' )
|
|
|
|
c->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
|
|
|
|
|
|
|
|
c->SetText( text );
|
|
|
|
table->AddCell( c );
|
|
|
|
};
|
|
|
|
|
2025-08-07 02:38:07 +08:00
|
|
|
const auto layerThicknessString =
|
|
|
|
[&]( const BOARD_STACKUP_ITEM& aStackupItem, int aSublayerId )
|
|
|
|
{
|
|
|
|
const int layerThickness = aStackupItem.GetThickness( aSublayerId );
|
|
|
|
|
|
|
|
// Layers like silkscreen, paste, etc. have no defined thickness, but that
|
|
|
|
// does not mean that they are specified as exactly 0mm
|
|
|
|
if( !aStackupItem.IsThicknessEditable() )
|
|
|
|
return NotSpecifiedPrm();
|
|
|
|
|
|
|
|
return m_frame->StringFromValue( layerThickness, true );
|
|
|
|
};
|
|
|
|
|
2025-04-12 14:39:00 +01:00
|
|
|
addHeaderCell( _( "Layer Name" ) );
|
|
|
|
addHeaderCell( _( "Type" ) );
|
|
|
|
addHeaderCell( _( "Material" ) );
|
|
|
|
addHeaderCell( _( "Thickness" ) );
|
|
|
|
addHeaderCell( _( "Color" ) );
|
|
|
|
addHeaderCell( _( "Epsilon R" ) );
|
|
|
|
addHeaderCell( _( "Loss Tangent" ) );
|
|
|
|
|
|
|
|
for( int i = 0; i < stackup.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
|
|
|
|
|
|
|
|
for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
|
|
|
|
{
|
|
|
|
// Layer names are empty until we close at least once the board setup dialog.
|
|
|
|
// If the user did not open the dialog, then get the names from the board.
|
|
|
|
// But dielectric layer names will be missing.
|
|
|
|
// In this case, for dielectric, a dummy name will be used
|
|
|
|
if( stackup_item->GetLayerName().IsEmpty() )
|
|
|
|
{
|
|
|
|
wxString layerName;
|
|
|
|
|
|
|
|
if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
|
|
|
|
layerName = m_frame->GetBoard()->GetLayerName( stackup_item->GetBrdLayerId() );
|
|
|
|
|
|
|
|
if( layerName.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
|
|
|
|
layerName = _( "Dielectric" );
|
|
|
|
|
|
|
|
addDataCell( layerName );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
addDataCell( stackup_item->GetLayerName() );
|
|
|
|
}
|
|
|
|
|
|
|
|
addDataCell( InitialCaps( stackup_item->GetTypeName() ) );
|
|
|
|
addDataCell( stackup_item->GetMaterial( sublayer_id ) );
|
2025-08-07 02:38:07 +08:00
|
|
|
addDataCell( layerThicknessString( *stackup_item, sublayer_id ), 'R' );
|
2025-04-12 14:39:00 +01:00
|
|
|
addDataCell( stackup_item->GetColor( sublayer_id ) );
|
|
|
|
addDataCell( EDA_UNIT_UTILS::UI::StringFromValue( unityScale, EDA_UNITS::UNSCALED,
|
|
|
|
stackup_item->GetEpsilonR( sublayer_id ) ), 'R' );
|
|
|
|
addDataCell( EDA_UNIT_UTILS::UI::StringFromValue( unityScale, EDA_UNITS::UNSCALED,
|
|
|
|
stackup_item->GetLossTangent( sublayer_id ) ), 'R' );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
table->Autosize();
|
|
|
|
|
|
|
|
std::vector<BOARD_ITEM*> items;
|
|
|
|
items.push_back( table );
|
|
|
|
|
2025-08-19 13:57:07 -04:00
|
|
|
if( placeBoardItems( &commit, items, true, true, false, false ) )
|
2025-04-12 14:39:00 +01:00
|
|
|
commit.Push( _( "Place Board Stackup Table" ) );
|
|
|
|
else
|
|
|
|
delete table;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-02-10 17:05:47 +00:00
|
|
|
int PCB_CONTROL::FlipPcbView( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
view()->SetMirror( !view()->IsMirroredX(), false );
|
|
|
|
view()->RecacheAllItems();
|
2025-07-13 10:47:21 +01:00
|
|
|
m_frame->GetCanvas()->ForceRefresh();
|
|
|
|
m_frame->OnDisplayOptionsChanged();
|
2021-02-10 17:05:47 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2025-04-07 11:16:13 +01:00
|
|
|
|
|
|
|
void PCB_CONTROL::rehatchBoardItem( BOARD_ITEM* aItem )
|
|
|
|
{
|
|
|
|
if( aItem->Type() == PCB_SHAPE_T )
|
|
|
|
{
|
|
|
|
static_cast<PCB_SHAPE*>( aItem )->UpdateHatching();
|
|
|
|
|
|
|
|
if( view() )
|
|
|
|
view()->Update( aItem );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PCB_CONTROL::RehatchShapes( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
for( FOOTPRINT* footprint : board()->Footprints() )
|
|
|
|
footprint->RunOnChildren( std::bind( &PCB_CONTROL::rehatchBoardItem, this, _1 ), NO_RECURSE );
|
|
|
|
|
|
|
|
for( BOARD_ITEM* item : board()->Drawings() )
|
|
|
|
rehatchBoardItem( item );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-08-10 20:19:32 -07:00
|
|
|
int PCB_CONTROL::CollectAndEmbed3DModels( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
BOARD* brd = board();
|
|
|
|
|
|
|
|
if( !brd )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
PROJECT& prj = m_frame->Prj();
|
|
|
|
S3D_CACHE* cache = PROJECT_PCB::Get3DCacheManager( &prj );
|
|
|
|
FILENAME_RESOLVER* resolver = cache ? cache->GetResolver() : nullptr;
|
|
|
|
|
|
|
|
wxString workingPath = prj.GetProjectPath();
|
|
|
|
std::vector<const EMBEDDED_FILES*> stack;
|
|
|
|
stack.push_back( brd->GetEmbeddedFiles() );
|
|
|
|
|
|
|
|
BOARD_COMMIT commit( m_frame );
|
|
|
|
int embeddedCount = 0;
|
|
|
|
|
|
|
|
for( FOOTPRINT* fp : brd->Footprints() )
|
|
|
|
{
|
|
|
|
bool fpModified = false;
|
|
|
|
|
|
|
|
for( FP_3DMODEL& model : fp->Models() )
|
|
|
|
{
|
|
|
|
if( model.m_Filename.StartsWith( FILEEXT::KiCadUriPrefix ) )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
wxString fullPath =
|
|
|
|
resolver ? resolver->ResolvePath( model.m_Filename, workingPath, stack ) : model.m_Filename;
|
|
|
|
wxFileName fname( fullPath );
|
|
|
|
|
|
|
|
if( fname.Exists() )
|
|
|
|
{
|
|
|
|
if( EMBEDDED_FILES::EMBEDDED_FILE* file = brd->GetEmbeddedFiles()->AddFile( fname, false ) )
|
|
|
|
{
|
|
|
|
model.m_Filename = file->GetLink();
|
|
|
|
fpModified = true;
|
|
|
|
embeddedCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( fpModified )
|
|
|
|
commit.Modify( fp );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( embeddedCount > 0 )
|
|
|
|
{
|
|
|
|
commit.Push( _( "Embed 3D Models" ) );
|
|
|
|
wxString msg = wxString::Format( _( "%d 3D model(s) successfully embedded." ), embeddedCount );
|
|
|
|
m_frame->GetInfoBar()->ShowMessageFor( msg, 5000 );
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-07-31 19:31:07 +08:00
|
|
|
// clang-format off
|
2020-12-16 13:31:32 +00:00
|
|
|
void PCB_CONTROL::setTransitions()
|
2014-03-24 08:45:05 +01:00
|
|
|
{
|
2020-12-16 13:31:32 +00:00
|
|
|
Go( &PCB_CONTROL::AddLibrary, ACTIONS::newLibrary.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::AddLibrary, ACTIONS::addLibrary.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::Print, ACTIONS::print.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::Quit, ACTIONS::quit.MakeEvent() );
|
2019-12-23 18:11:45 +01:00
|
|
|
|
2025-02-20 16:21:40 +00:00
|
|
|
// Footprint library actions
|
|
|
|
Go( &PCB_CONTROL::SaveFpToBoard, PCB_ACTIONS::saveFpToBoard.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LoadFpFromBoard, PCB_ACTIONS::loadFpFromBoard.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::IterateFootprint, PCB_ACTIONS::nextFootprint.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::IterateFootprint, PCB_ACTIONS::previousFootprint.MakeEvent() );
|
|
|
|
|
2014-03-24 08:45:05 +01:00
|
|
|
// Display modes
|
2020-12-16 13:31:32 +00:00
|
|
|
Go( &PCB_CONTROL::TrackDisplayMode, PCB_ACTIONS::trackDisplayMode.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::ToggleRatsnest, PCB_ACTIONS::showRatsnest.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::ToggleRatsnest, PCB_ACTIONS::ratsnestLineMode.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::ViaDisplayMode, PCB_ACTIONS::viaDisplayMode.MakeEvent() );
|
2021-07-26 18:56:11 +01:00
|
|
|
Go( &PCB_CONTROL::ZoneDisplayMode, PCB_ACTIONS::zoneDisplayFilled.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::ZoneDisplayMode, PCB_ACTIONS::zoneDisplayOutline.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::ZoneDisplayMode, PCB_ACTIONS::zoneDisplayFractured.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::ZoneDisplayMode, PCB_ACTIONS::zoneDisplayTriangulated.MakeEvent() );
|
2020-12-16 13:31:32 +00:00
|
|
|
Go( &PCB_CONTROL::ZoneDisplayMode, PCB_ACTIONS::zoneDisplayToggle.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::HighContrastMode, ACTIONS::highContrastMode.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::HighContrastModeCycle, ACTIONS::highContrastModeCycle.MakeEvent() );
|
2023-06-17 23:47:26 -04:00
|
|
|
Go( &PCB_CONTROL::ContrastModeFeedback, EVENTS::ContrastModeChangedByKeyEvent );
|
2021-10-04 13:42:08 +01:00
|
|
|
Go( &PCB_CONTROL::NetColorModeCycle, PCB_ACTIONS::netColorModeCycle.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::RatsnestModeCycle, PCB_ACTIONS::ratsnestModeCycle.MakeEvent() );
|
2021-02-10 17:05:47 +00:00
|
|
|
Go( &PCB_CONTROL::FlipPcbView, PCB_ACTIONS::flipBoard.MakeEvent() );
|
2025-04-07 11:16:13 +01:00
|
|
|
Go( &PCB_CONTROL::RehatchShapes, PCB_ACTIONS::rehatchShapes.MakeEvent() );
|
2014-03-24 08:45:05 +01:00
|
|
|
|
|
|
|
// Layer control
|
2020-12-16 13:31:32 +00:00
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerTop.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner1.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner2.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner3.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner4.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner5.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner6.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner7.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner8.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner9.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner10.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner11.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner12.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner13.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner14.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner15.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner16.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner17.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner18.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner19.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner20.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner21.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner22.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner23.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner24.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner25.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner26.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner27.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner28.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner29.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner30.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerBottom.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerNext, PCB_ACTIONS::layerNext.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerPrev, PCB_ACTIONS::layerPrev.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerToggle, PCB_ACTIONS::layerToggle.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerAlphaInc, PCB_ACTIONS::layerAlphaInc.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::LayerAlphaDec, PCB_ACTIONS::layerAlphaDec.MakeEvent() );
|
2014-03-24 08:45:05 +01:00
|
|
|
|
2024-07-31 19:31:07 +08:00
|
|
|
Go( &PCB_CONTROL::CycleLayerPresets, PCB_ACTIONS::layerPairPresetsCycle.MakeEvent() );
|
2024-12-30 18:58:27 +00:00
|
|
|
Go( &PCB_CONTROL::LayerPresetFeedback, PCB_EVENTS::LayerPairPresetChangedByKeyEvent() );
|
2024-07-31 19:31:07 +08:00
|
|
|
|
2014-03-24 08:45:05 +01:00
|
|
|
// Grid control
|
2023-08-26 13:29:24 +01:00
|
|
|
Go( &PCB_CONTROL::GridPlaceOrigin, ACTIONS::gridSetOrigin.MakeEvent() );
|
2020-12-16 13:31:32 +00:00
|
|
|
Go( &PCB_CONTROL::GridResetOrigin, ACTIONS::gridResetOrigin.MakeEvent() );
|
2014-03-24 08:45:05 +01:00
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
Go( &PCB_CONTROL::Undo, ACTIONS::undo.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::Redo, ACTIONS::redo.MakeEvent() );
|
2019-05-14 20:21:10 +01:00
|
|
|
|
2023-08-17 09:25:26 -04:00
|
|
|
// Snapping control
|
|
|
|
Go( &PCB_CONTROL::SnapMode, PCB_ACTIONS::magneticSnapActiveLayer.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::SnapMode, PCB_ACTIONS::magneticSnapAllLayers.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::SnapMode, PCB_ACTIONS::magneticSnapToggle.MakeEvent() );
|
2024-12-30 18:58:27 +00:00
|
|
|
Go( &PCB_CONTROL::SnapModeFeedback, PCB_EVENTS::SnappingModeChangedByKeyEvent() );
|
2023-08-17 09:25:26 -04:00
|
|
|
|
2014-03-24 08:45:05 +01:00
|
|
|
// Miscellaneous
|
2025-08-10 20:19:32 -07:00
|
|
|
Go( &PCB_CONTROL::InteractiveDelete, ACTIONS::deleteTool.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::CollectAndEmbed3DModels, PCB_ACTIONS::collect3DModels.MakeEvent() );
|
2017-04-15 18:08:23 +02:00
|
|
|
|
|
|
|
// Append control
|
2025-03-20 11:57:59 -04:00
|
|
|
Go( &PCB_CONTROL::AppendDesignBlock, PCB_ACTIONS::placeDesignBlock.MakeEvent() );
|
2025-08-19 13:57:07 -04:00
|
|
|
Go( &PCB_CONTROL::ApplyDesignBlockLayout, PCB_ACTIONS::applyDesignBlockLayout.MakeEvent() );
|
2025-05-08 13:30:31 -04:00
|
|
|
Go( &PCB_CONTROL::PlaceLinkedDesignBlock, PCB_ACTIONS::placeLinkedDesignBlock.MakeEvent() );
|
2025-05-15 14:26:37 -04:00
|
|
|
Go( &PCB_CONTROL::SaveToLinkedDesignBlock, PCB_ACTIONS::saveToLinkedDesignBlock.MakeEvent() );
|
2020-12-16 13:31:32 +00:00
|
|
|
Go( &PCB_CONTROL::AppendBoardFromFile, PCB_ACTIONS::appendBoard.MakeEvent() );
|
2022-09-14 22:28:09 +00:00
|
|
|
Go( &PCB_CONTROL::DdAppendBoard, PCB_ACTIONS::ddAppendBoard.MakeEvent() );
|
2025-04-12 14:39:00 +01:00
|
|
|
Go( &PCB_CONTROL::PlaceCharacteristics, PCB_ACTIONS::placeCharacteristics.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::PlaceStackup, PCB_ACTIONS::placeStackup.MakeEvent() );
|
2017-09-17 19:49:06 +02:00
|
|
|
|
2020-12-16 13:31:32 +00:00
|
|
|
Go( &PCB_CONTROL::Paste, ACTIONS::paste.MakeEvent() );
|
2021-05-01 23:44:22 +01:00
|
|
|
Go( &PCB_CONTROL::Paste, ACTIONS::pasteSpecial.MakeEvent() );
|
2019-05-29 00:23:58 +01:00
|
|
|
|
2022-07-19 18:00:35 +03:00
|
|
|
Go( &PCB_CONTROL::UpdateMessagePanel, EVENTS::PointSelectedEvent );
|
2020-12-16 13:31:32 +00:00
|
|
|
Go( &PCB_CONTROL::UpdateMessagePanel, EVENTS::SelectedEvent );
|
|
|
|
Go( &PCB_CONTROL::UpdateMessagePanel, EVENTS::UnselectedEvent );
|
|
|
|
Go( &PCB_CONTROL::UpdateMessagePanel, EVENTS::ClearedEvent );
|
|
|
|
Go( &PCB_CONTROL::UpdateMessagePanel, EVENTS::SelectedItemsModified );
|
2022-11-18 00:45:14 +00:00
|
|
|
Go( &PCB_CONTROL::UpdateMessagePanel, EVENTS::ConnectivityChangedEvent );
|
2022-09-14 22:28:09 +00:00
|
|
|
|
|
|
|
// Add library by dropping file
|
|
|
|
Go( &PCB_CONTROL::DdAddLibrary, ACTIONS::ddAddLibrary.MakeEvent() );
|
|
|
|
Go( &PCB_CONTROL::DdImportFootprint, PCB_ACTIONS::ddImportFootprint.MakeEvent() );
|
2014-03-24 08:45:05 +01:00
|
|
|
}
|
2025-03-20 11:57:59 -04:00
|
|
|
// clang-format on
|