kicad-source/pcbnew/tools/pcb_editor_control.cpp
Jeff Young 92dcf43ae8 UNIT_BINDERize zone dialogs.
Also includes a bunch of changes to simplifiy the terminology
and unify the copper, non-copper and keepout versions.

Also removes some legacy features:
Removes contol for fill method.  If the fill method is currently
segments the dialog will ask if you want to convert to polygons
on OK.
Removes control of boundary resolution.  We've done this with trig
since 5.0.

(cherry picked from commit 487aaeb)
2018-07-17 15:11:32 +01:00

1214 lines
37 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2014 CERN
* Copyright (C) 2014-2017 KiCad Developers, see AUTHORS.txt for contributors.
* @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
*/
#include <cstdint>
#include <thread>
#include <mutex>
#include "pcb_editor_control.h"
#include "pcb_actions.h"
#include <tool/tool_manager.h>
#include <wx/progdlg.h>
#include "edit_tool.h"
#include "selection_tool.h"
#include "drawing_tool.h"
#include "picker_tool.h"
#include <painter.h>
#include <project.h>
#include <pcbnew_id.h>
#include <pcb_edit_frame.h>
#include <class_board.h>
#include <class_zone.h>
#include <pcb_draw_panel_gal.h>
#include <class_module.h>
#include <class_pcb_target.h>
#include <connectivity_data.h>
#include <collectors.h>
#include <zones_functions_for_undo_redo.h>
#include <board_commit.h>
#include <confirm.h>
#include <bitmaps.h>
#include <hotkeys.h>
#include <view/view_group.h>
#include <view/view_controls.h>
#include <origin_viewitem.h>
#include <profile.h>
#include <widgets/progress_reporter.h>
#ifdef USE_OPENMP
#include <omp.h>
#endif /* USE_OPENMP */
#include <tools/tool_event_utils.h>
#include <functional>
using namespace std::placeholders;
// Track & via size control
TOOL_ACTION PCB_ACTIONS::trackWidthInc( "pcbnew.EditorControl.trackWidthInc",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_SWITCH_TRACK_WIDTH_TO_NEXT ),
"", "" );
TOOL_ACTION PCB_ACTIONS::trackWidthDec( "pcbnew.EditorControl.trackWidthDec",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_SWITCH_TRACK_WIDTH_TO_PREVIOUS ),
"", "" );
TOOL_ACTION PCB_ACTIONS::viaSizeInc( "pcbnew.EditorControl.viaSizeInc",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_VIA_SIZE_INC ),
"", "" );
TOOL_ACTION PCB_ACTIONS::viaSizeDec( "pcbnew.EditorControl.viaSizeDec",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_VIA_SIZE_DEC ),
"", "" );
TOOL_ACTION PCB_ACTIONS::trackViaSizeChanged( "pcbnew.EditorControl.trackViaSizeChanged",
AS_GLOBAL, 0,
"", "", NULL, AF_NOTIFY );
TOOL_ACTION PCB_ACTIONS::zoneMerge( "pcbnew.EditorControl.zoneMerge",
AS_GLOBAL, 0,
_( "Merge Zones" ), _( "Merge zones" ) );
TOOL_ACTION PCB_ACTIONS::zoneDuplicate( "pcbnew.EditorControl.zoneDuplicate",
AS_GLOBAL, 0,
_( "Duplicate Zone onto Layer..." ), _( "Duplicate zone outline onto a different layer" ),
zone_duplicate_xpm );
TOOL_ACTION PCB_ACTIONS::placeTarget( "pcbnew.EditorControl.placeTarget",
AS_GLOBAL, 0,
_( "Add Layer Alignment Target" ), _( "Add a layer alignment target" ), NULL, AF_ACTIVATE );
TOOL_ACTION PCB_ACTIONS::placeModule( "pcbnew.EditorControl.placeModule",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_ADD_MODULE ),
_( "Add Footprint" ), _( "Add a footprint" ), NULL, AF_ACTIVATE );
TOOL_ACTION PCB_ACTIONS::drillOrigin( "pcbnew.EditorControl.drillOrigin",
AS_GLOBAL, 0,
"", "" );
TOOL_ACTION PCB_ACTIONS::crossProbeSchToPcb( "pcbnew.EditorControl.crossProbSchToPcb",
AS_GLOBAL, 0,
"", "" );
TOOL_ACTION PCB_ACTIONS::toggleLock( "pcbnew.EditorControl.toggleLock",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_LOCK_UNLOCK_FOOTPRINT ),
"Toggle Lock", "", lock_unlock_xpm );
TOOL_ACTION PCB_ACTIONS::lock( "pcbnew.EditorControl.lock",
AS_GLOBAL, 0,
_( "Lock" ), "", locked_xpm );
TOOL_ACTION PCB_ACTIONS::unlock( "pcbnew.EditorControl.unlock",
AS_GLOBAL, 0,
_( "Unlock" ), "", unlocked_xpm );
TOOL_ACTION PCB_ACTIONS::appendBoard( "pcbnew.EditorControl.appendBoard",
AS_GLOBAL, 0,
"", "" );
TOOL_ACTION PCB_ACTIONS::highlightNet( "pcbnew.EditorControl.highlightNet",
AS_GLOBAL, 0,
"", "" );
TOOL_ACTION PCB_ACTIONS::highlightNetCursor( "pcbnew.EditorControl.highlightNetCursor",
AS_GLOBAL, 0,
"", "" );
TOOL_ACTION PCB_ACTIONS::highlightNetSelection( "pcbnew.EditorControl.highlightNetSelection",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_HIGHLIGHT_NET_SELECTION ),
"", "" );
TOOL_ACTION PCB_ACTIONS::showLocalRatsnest( "pcbnew.Control.showLocalRatsnest",
AS_GLOBAL, 0,
"", "" );
class ZONE_CONTEXT_MENU : public CONTEXT_MENU
{
public:
ZONE_CONTEXT_MENU()
{
SetIcon( add_zone_xpm );
SetTitle( _( "Zones" ) );
Add( PCB_ACTIONS::zoneFill );
Add( PCB_ACTIONS::zoneFillAll );
Add( PCB_ACTIONS::zoneUnfill );
Add( PCB_ACTIONS::zoneUnfillAll );
AppendSeparator();
Add( PCB_ACTIONS::zoneMerge );
Add( PCB_ACTIONS::zoneDuplicate );
Add( PCB_ACTIONS::drawZoneCutout );
Add( PCB_ACTIONS::drawSimilarZone );
}
protected:
CONTEXT_MENU* create() const override
{
return new ZONE_CONTEXT_MENU();
}
private:
void update() override
{
SELECTION_TOOL* selTool = getToolManager()->GetTool<SELECTION_TOOL>();
// enable zone actions that act on a single zone
bool singleZoneActionsEnabled = ( SELECTION_CONDITIONS::Count( 1 )
&& SELECTION_CONDITIONS::OnlyType( PCB_ZONE_AREA_T )
)( selTool->GetSelection() );
Enable( getMenuId( PCB_ACTIONS::zoneDuplicate ), singleZoneActionsEnabled );
Enable( getMenuId( PCB_ACTIONS::drawZoneCutout ), singleZoneActionsEnabled );
Enable( getMenuId( PCB_ACTIONS::drawSimilarZone ), singleZoneActionsEnabled );
// enable zone actions that ably to a specific set of zones (as opposed to all of them)
bool nonGlobalActionsEnabled = ( SELECTION_CONDITIONS::MoreThan( 0 ) )( selTool->GetSelection() );
Enable( getMenuId( PCB_ACTIONS::zoneFill ), nonGlobalActionsEnabled );
Enable( getMenuId( PCB_ACTIONS::zoneUnfill ), nonGlobalActionsEnabled );
// lines like this make me really think about a better name for SELECTION_CONDITIONS class
bool mergeEnabled = ( SELECTION_CONDITIONS::MoreThan( 1 ) &&
/*SELECTION_CONDITIONS::OnlyType( PCB_ZONE_AREA_T ) &&*/
PCB_SELECTION_CONDITIONS::SameNet( true ) &&
PCB_SELECTION_CONDITIONS::SameLayer() )( selTool->GetSelection() );
Enable( getMenuId( PCB_ACTIONS::zoneMerge ), mergeEnabled );
}
};
class LOCK_CONTEXT_MENU : public CONTEXT_MENU
{
public:
LOCK_CONTEXT_MENU()
{
SetIcon( locked_xpm );
SetTitle( _( "Locking" ) );
Add( PCB_ACTIONS::lock );
Add( PCB_ACTIONS::unlock );
Add( PCB_ACTIONS::toggleLock );
}
CONTEXT_MENU* create() const override
{
return new LOCK_CONTEXT_MENU();
}
};
PCB_EDITOR_CONTROL::PCB_EDITOR_CONTROL() :
PCB_TOOL( "pcbnew.EditorControl" ),
m_frame( nullptr ),
m_menu( *this )
{
m_placeOrigin.reset( new KIGFX::ORIGIN_VIEWITEM( KIGFX::COLOR4D( 0.8, 0.0, 0.0, 1.0 ),
KIGFX::ORIGIN_VIEWITEM::CIRCLE_CROSS ) );
m_probingSchToPcb = false;
m_slowRatsnest = false;
}
PCB_EDITOR_CONTROL::~PCB_EDITOR_CONTROL()
{
}
void PCB_EDITOR_CONTROL::Reset( RESET_REASON aReason )
{
m_frame = getEditFrame<PCB_EDIT_FRAME>();
if( aReason == MODEL_RELOAD || aReason == GAL_SWITCH )
{
m_placeOrigin->SetPosition( getModel<BOARD>()->GetAuxOrigin() );
getView()->Remove( m_placeOrigin.get() );
getView()->Add( m_placeOrigin.get() );
}
}
bool PCB_EDITOR_CONTROL::Init()
{
auto activeToolCondition = [ this ] ( const SELECTION& aSel ) {
return ( m_frame->GetToolId() != ID_NO_TOOL_SELECTED );
};
auto inactiveStateCondition = [ this ] ( const SELECTION& aSel ) {
return ( m_frame->GetToolId() == ID_NO_TOOL_SELECTED && aSel.Size() == 0 );
};
auto placeModuleCondition = [ this ] ( const SELECTION& aSel ) {
return ( m_frame->GetToolId() == ID_PCB_MODULE_BUTT && aSel.GetSize() == 0 );
};
auto& ctxMenu = m_menu.GetMenu();
// "Cancel" goes at the top of the context menu when a tool is active
ctxMenu.AddItem( ACTIONS::cancelInteractive, activeToolCondition, 1000 );
ctxMenu.AddSeparator( activeToolCondition, 1000 );
// "Get and Place Footprint" should be available for Place Footprint tool
ctxMenu.AddItem( PCB_ACTIONS::findMove, placeModuleCondition, 1000 );
ctxMenu.AddSeparator( placeModuleCondition, 1000 );
// Finally, add the standard zoom & grid items
m_menu.AddStandardSubMenus( *getEditFrame<PCB_BASE_FRAME>() );
auto zoneMenu = std::make_shared<ZONE_CONTEXT_MENU>();
zoneMenu->SetTool( this );
auto lockMenu = std::make_shared<LOCK_CONTEXT_MENU>();
lockMenu->SetTool( this );
// Add the PCB control menus to relevant other tools
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
if( selTool )
{
auto& toolMenu = selTool->GetToolMenu();
auto& menu = toolMenu.GetMenu();
// Add "Get and Place Footprint" when Selection tool is in an inactive state
menu.AddItem( PCB_ACTIONS::findMove, inactiveStateCondition );
menu.AddSeparator( inactiveStateCondition );
toolMenu.AddSubMenu( zoneMenu );
toolMenu.AddSubMenu( lockMenu );
menu.AddMenu( zoneMenu.get(), false,
SELECTION_CONDITIONS::OnlyType( PCB_ZONE_AREA_T ) );
menu.AddMenu( lockMenu.get(), false,
SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::LockableItems ) );
}
DRAWING_TOOL* drawingTool = m_toolMgr->GetTool<DRAWING_TOOL>();
if( drawingTool )
{
auto& toolMenu = drawingTool->GetToolMenu();
auto& menu = toolMenu.GetMenu();
toolMenu.AddSubMenu( zoneMenu );
// Functor to say if the PCB_EDIT_FRAME is in a given mode
// Capture the tool pointer and tool mode by value
auto toolActiveFunctor = [=]( DRAWING_TOOL::MODE aMode )
{
return [=]( const SELECTION& sel )
{
return drawingTool->GetDrawingMode() == aMode;
};
};
menu.AddMenu( zoneMenu.get(), false, toolActiveFunctor( DRAWING_TOOL::MODE::ZONE ) );
}
m_ratsnestTimer.SetOwner( this );
Connect( m_ratsnestTimer.GetId(), wxEVT_TIMER,
wxTimerEventHandler( PCB_EDITOR_CONTROL::ratsnestTimer ), NULL, this );
return true;
}
// Track & via size control
int PCB_EDITOR_CONTROL::TrackWidthInc( const TOOL_EVENT& aEvent )
{
BOARD* board = getModel<BOARD>();
int widthIndex = board->GetDesignSettings().GetTrackWidthIndex() + 1;
if( widthIndex >= (int) board->GetDesignSettings().m_TrackWidthList.size() )
widthIndex = board->GetDesignSettings().m_TrackWidthList.size() - 1;
board->GetDesignSettings().SetTrackWidthIndex( widthIndex );
board->GetDesignSettings().UseCustomTrackViaSize( false );
m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged );
return 0;
}
int PCB_EDITOR_CONTROL::TrackWidthDec( const TOOL_EVENT& aEvent )
{
BOARD* board = getModel<BOARD>();
int widthIndex = board->GetDesignSettings().GetTrackWidthIndex() - 1;
if( widthIndex < 0 )
widthIndex = 0;
board->GetDesignSettings().SetTrackWidthIndex( widthIndex );
board->GetDesignSettings().UseCustomTrackViaSize( false );
m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged );
return 0;
}
int PCB_EDITOR_CONTROL::ViaSizeInc( const TOOL_EVENT& aEvent )
{
BOARD* board = getModel<BOARD>();
int sizeIndex = board->GetDesignSettings().GetViaSizeIndex() + 1;
if( sizeIndex >= (int) board->GetDesignSettings().m_ViasDimensionsList.size() )
sizeIndex = board->GetDesignSettings().m_ViasDimensionsList.size() - 1;
board->GetDesignSettings().SetViaSizeIndex( sizeIndex );
board->GetDesignSettings().UseCustomTrackViaSize( false );
m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged );
return 0;
}
int PCB_EDITOR_CONTROL::ViaSizeDec( const TOOL_EVENT& aEvent )
{
BOARD* board = getModel<BOARD>();
int sizeIndex = board->GetDesignSettings().GetViaSizeIndex() - 1;
if( sizeIndex < 0 )
sizeIndex = 0;
board->GetDesignSettings().SetViaSizeIndex( sizeIndex );
board->GetDesignSettings().UseCustomTrackViaSize( false );
m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged );
return 0;
}
int PCB_EDITOR_CONTROL::PlaceModule( const TOOL_EVENT& aEvent )
{
MODULE* module = aEvent.Parameter<MODULE*>();
KIGFX::VIEW_CONTROLS* controls = getViewControls();
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
SELECTION& selection = selTool->GetSelection();
BOARD_COMMIT commit( m_frame );
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
controls->ShowCursor( true );
controls->SetSnapping( true );
Activate();
m_frame->SetToolID( ID_PCB_MODULE_BUTT, wxCURSOR_PENCIL, _( "Add footprint" ) );
// Add all the drawable parts to preview
VECTOR2I cursorPos = controls->GetCursorPosition();
if( module )
{
module->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, module );
}
bool reselect = false;
// Main loop: keep receiving events
while( OPT_TOOL_EVENT evt = Wait() )
{
cursorPos = controls->GetCursorPosition();
if( reselect && module )
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, module );
if( evt->IsCancel() || TOOL_EVT_UTILS::IsCancelInteractive( *evt ) || evt->IsActivate() )
{
if( module )
{
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
commit.Revert();
module = NULL;
}
else // let's have another chance placing a module
break;
if( evt->IsActivate() ) // now finish unconditionally
break;
}
else if( evt->IsClick( BUT_LEFT ) )
{
if( !module )
{
// Pick the module to be placed
module = m_frame->LoadModuleFromLibrary( wxEmptyString );
if( module == NULL )
continue;
m_frame->AddModuleToBoard( module );
commit.Added( module );
module->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, module );
controls->SetCursorPosition( cursorPos, false );
}
else
{
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
commit.Push( _( "Place a module" ) );
module = NULL; // to indicate that there is no module that we currently modify
}
}
else if( evt->IsClick( BUT_RIGHT ) )
{
m_menu.ShowContextMenu( selTool->GetSelection() );
}
else if( module && evt->IsMotion() )
{
module->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
selection.SetReferencePoint( cursorPos );
getView()->Update( &selection );
}
else if( module && evt->IsAction( &PCB_ACTIONS::properties ) )
{
// Calling 'Properties' action clears the selection, so we need to restore it
reselect = true;
}
// Enable autopanning and cursor capture only when there is a module to be placed
controls->SetAutoPan( !!module );
controls->CaptureCursor( !!module );
}
m_frame->SetNoToolSelected();
return 0;
}
int PCB_EDITOR_CONTROL::ToggleLockSelected( const TOOL_EVENT& aEvent )
{
return modifyLockSelected( TOGGLE );
}
int PCB_EDITOR_CONTROL::LockSelected( const TOOL_EVENT& aEvent )
{
return modifyLockSelected( ON );
}
int PCB_EDITOR_CONTROL::UnlockSelected( const TOOL_EVENT& aEvent )
{
return modifyLockSelected( OFF );
}
int PCB_EDITOR_CONTROL::modifyLockSelected( MODIFY_MODE aMode )
{
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
const SELECTION& selection = selTool->GetSelection();
if( selection.Empty() )
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
bool modified = false;
for( auto i : selection )
{
auto item = static_cast<BOARD_ITEM*>( i );
bool prevState = item->IsLocked();
switch( aMode )
{
case ON:
item->SetLocked( true );
break;
case OFF:
item->SetLocked( false );
break;
case TOGGLE:
item->SetLocked( !prevState );
break;
}
// Check if we really modified an item
if( !modified && prevState != item->IsLocked() )
modified = true;
}
if( modified )
m_frame->OnModify();
return 0;
}
int PCB_EDITOR_CONTROL::PlaceTarget( const TOOL_EVENT& aEvent )
{
auto selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
KIGFX::VIEW* view = getView();
KIGFX::VIEW_CONTROLS* controls = getViewControls();
BOARD* board = getModel<BOARD>();
PCB_TARGET* target = new PCB_TARGET( board );
// Init the new item attributes
target->SetLayer( Edge_Cuts );
target->SetWidth( board->GetDesignSettings().m_EdgeSegmentWidth );
target->SetSize( Millimeter2iu( 5 ) );
VECTOR2I cursorPos = controls->GetCursorPosition();
target->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
// Add a VIEW_GROUP that serves as a preview for the new item
KIGFX::VIEW_GROUP preview( view );
preview.Add( target );
view->Add( &preview );
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
controls->SetSnapping( true );
Activate();
m_frame->SetToolID( ID_PCB_TARGET_BUTT, wxCURSOR_PENCIL, _( "Add layer alignment target" ) );
// Main loop: keep receiving events
while( OPT_TOOL_EVENT evt = Wait() )
{
cursorPos = controls->GetCursorPosition();
if( evt->IsCancel() || TOOL_EVT_UTILS::IsCancelInteractive( *evt ) || evt->IsActivate() )
break;
else if( evt->IsAction( &PCB_ACTIONS::incWidth ) )
{
target->SetWidth( target->GetWidth() + WIDTH_STEP );
view->Update( &preview );
}
else if( evt->IsAction( &PCB_ACTIONS::decWidth ) )
{
int width = target->GetWidth();
if( width > WIDTH_STEP )
{
target->SetWidth( width - WIDTH_STEP );
view->Update( &preview );
}
}
else if( evt->IsClick( BUT_LEFT ) )
{
assert( target->GetSize() > 0 );
assert( target->GetWidth() > 0 );
BOARD_COMMIT commit( m_frame );
commit.Add( target );
commit.Push( _( "Place a layer alignment target" ) );
preview.Remove( target );
// Create next PCB_TARGET
target = new PCB_TARGET( *target );
preview.Add( target );
}
else if( evt->IsClick( BUT_RIGHT ) )
{
m_menu.ShowContextMenu( selTool->GetSelection() );
}
else if( evt->IsMotion() )
{
target->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
view->Update( &preview );
}
}
delete target;
controls->SetSnapping( false );
view->Remove( &preview );
m_frame->SetNoToolSelected();
return 0;
}
static bool mergeZones( BOARD_COMMIT& aCommit, std::vector<ZONE_CONTAINER *>& aOriginZones,
std::vector<ZONE_CONTAINER *>& aMergedZones )
{
for( unsigned int i = 1; i < aOriginZones.size(); i++ )
{
aOriginZones[0]->Outline()->BooleanAdd( *aOriginZones[i]->Outline(),
SHAPE_POLY_SET::PM_FAST );
}
aOriginZones[0]->Outline()->Simplify( SHAPE_POLY_SET::PM_FAST );
// We should have one polygon with hole
// We can have 2 polygons with hole, if the 2 initial polygons have only one common corner
// and therefore cannot be merged (they are dectected as intersecting)
// but we should never have more than 2 polys
if( aOriginZones[0]->Outline()->OutlineCount() > 1 )
{
wxLogMessage( wxT( "BOARD::CombineAreas error: more than 2 polys after merging" ) );
return false;
}
for( unsigned int i = 1; i < aOriginZones.size(); i++ )
{
aCommit.Remove( aOriginZones[i] );
}
aCommit.Modify( aOriginZones[0] );
aMergedZones.push_back( aOriginZones[0] );
aOriginZones[0]->SetLocalFlags( 1 );
aOriginZones[0]->Hatch();
aOriginZones[0]->CacheTriangulation();
return true;
}
int PCB_EDITOR_CONTROL::ZoneMerge( const TOOL_EVENT& aEvent )
{
const SELECTION& selection = m_toolMgr->GetTool<SELECTION_TOOL>()->GetSelection();
BOARD* board = getModel<BOARD>();
BOARD_COMMIT commit( m_frame );
if( selection.Size() < 2 )
return 0;
int netcode = -1;
ZONE_CONTAINER* firstZone = nullptr;
std::vector<ZONE_CONTAINER*> toMerge, merged;
for( auto item : selection )
{
auto curr_area = dynamic_cast<ZONE_CONTAINER*>( item );
if( !curr_area )
continue;
if( !firstZone )
firstZone = curr_area;
netcode = curr_area->GetNetCode();
if( firstZone->GetNetCode() != netcode )
continue;
if( curr_area->GetPriority() != firstZone->GetPriority() )
continue;
if( curr_area->GetIsKeepout() != firstZone->GetIsKeepout() )
continue;
if( curr_area->GetLayer() != firstZone->GetLayer() )
continue;
if( !board->TestAreaIntersection( curr_area, firstZone ) )
continue;
toMerge.push_back( curr_area );
}
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
if( mergeZones( commit, toMerge, merged ) )
{
commit.Push( _( "Merge zones" ) );
for( auto item : merged )
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, item );
}
return 0;
}
int PCB_EDITOR_CONTROL::ZoneDuplicate( const TOOL_EVENT& aEvent )
{
auto selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
const auto& selection = selTool->GetSelection();
// because this pops up the zone editor, it would be confusing to handle multiple zones,
// so just handle single selections containing exactly one zone
if( selection.Size() != 1 )
return 0;
auto oldZone = dyn_cast<ZONE_CONTAINER*>( selection[0] );
if( !oldZone )
return 0;
ZONE_SETTINGS zoneSettings;
zoneSettings << *oldZone;
int dialogResult;
if( oldZone->GetIsKeepout() )
dialogResult = InvokeKeepoutAreaEditor( m_frame, &zoneSettings );
else if( oldZone->IsOnCopperLayer() )
dialogResult = InvokeCopperZonesEditor( m_frame, &zoneSettings );
else
dialogResult = InvokeNonCopperZonesEditor( m_frame, &zoneSettings );
if( dialogResult != wxID_OK )
return 0;
// duplicate the zone
BOARD_COMMIT commit( m_frame );
auto newZone = std::make_unique<ZONE_CONTAINER>( *oldZone );
newZone->ClearSelected();
newZone->UnFill();
zoneSettings.ExportSetting( *newZone );
// If the new zone is on the same layer(s) as the the initial zone,
// offset it a bit so it can more easily be picked.
if( oldZone->GetIsKeepout() && ( oldZone->GetLayerSet() == zoneSettings.m_Layers ) )
newZone->Move( wxPoint( IU_PER_MM, IU_PER_MM ) );
else if( !oldZone->GetIsKeepout() && ( oldZone->GetLayer() == zoneSettings.m_CurrentZone_Layer ) )
newZone->Move( wxPoint( IU_PER_MM, IU_PER_MM ) );
commit.Add( newZone.release() );
commit.Push( _( "Duplicate zone" ) );
return 0;
}
int PCB_EDITOR_CONTROL::CrossProbePcbToSch( const TOOL_EVENT& aEvent )
{
if( m_probingSchToPcb )
{
m_probingSchToPcb = false;
return 0;
}
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
const SELECTION& selection = selTool->GetSelection();
if( selection.Size() == 1 )
m_frame->SendMessageToEESCHEMA( static_cast<BOARD_ITEM*>( selection.Front() ) );
return 0;
}
int PCB_EDITOR_CONTROL::CrossProbeSchToPcb( const TOOL_EVENT& aEvent )
{
BOARD_ITEM* item = aEvent.Parameter<BOARD_ITEM*>();
if( item )
{
m_probingSchToPcb = true;
getView()->SetCenter( VECTOR2D( item->GetPosition() ) );
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
// If it is a pad and the net highlighting tool is enabled, highlight the net
if( item->Type() == PCB_PAD_T && m_frame->GetToolId() == ID_PCB_HIGHLIGHT_BUTT )
{
int net = static_cast<D_PAD*>( item )->GetNetCode();
m_toolMgr->RunAction( PCB_ACTIONS::highlightNet, false, net );
}
else
// Otherwise simply select the corresponding item
{
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, item );
// Ensure the display is refreshed, because in some installs
// the refresh is done only when the gal canvas has the focus, and
// that is not the case when crossprobing from Eeschema:
m_frame->GetGalCanvas()->Refresh();
}
}
return 0;
}
bool PCB_EDITOR_CONTROL::DoSetDrillOrigin( KIGFX::VIEW* aView, PCB_BASE_FRAME* aFrame,
BOARD_ITEM* originViewItem, const VECTOR2D& aPosition )
{
aFrame->SetAuxOrigin( wxPoint( aPosition.x, aPosition.y ) );
originViewItem->SetPosition( wxPoint( aPosition.x, aPosition.y ) );
aView->MarkDirty();
aFrame->OnModify();
return true;
}
bool PCB_EDITOR_CONTROL::SetDrillOrigin( KIGFX::VIEW* aView, PCB_BASE_FRAME* aFrame,
BOARD_ITEM* originViewItem, const VECTOR2D& aPosition )
{
aFrame->SaveCopyInUndoList( originViewItem, UR_DRILLORIGIN );
return DoSetDrillOrigin( aView, aFrame, originViewItem, aPosition );
}
int PCB_EDITOR_CONTROL::DrillOrigin( const TOOL_EVENT& aEvent )
{
Activate();
PICKER_TOOL* picker = m_toolMgr->GetTool<PICKER_TOOL>();
assert( picker );
m_frame->SetToolID( ID_PCB_PLACE_OFFSET_COORD_BUTT, wxCURSOR_HAND, _( "Adjust zero" ) );
picker->SetClickHandler( std::bind( SetDrillOrigin, getView(), m_frame, m_placeOrigin.get(), _1 ) );
picker->Activate();
Wait();
return 0;
}
/**
* Function highlightNet()
* Looks for a BOARD_CONNECTED_ITEM in a given spot, and if one is found - it enables
* highlight for its net.
* @param aToolMgr is the TOOL_MANAGER currently in use.
* @param aPosition is the point where an item is expected (world coordinates).
* @param aUseSelection is true if we should use the current selection to pick the netcode
*/
static bool highlightNet( TOOL_MANAGER* aToolMgr, const VECTOR2D& aPosition,
bool aUseSelection = false )
{
auto render = aToolMgr->GetView()->GetPainter()->GetSettings();
auto frame = static_cast<PCB_EDIT_FRAME*>( aToolMgr->GetEditFrame() );
BOARD* board = static_cast<BOARD*>( aToolMgr->GetModel() );
int net = -1;
bool enableHighlight = false;
if( aUseSelection )
{
auto selectionTool = aToolMgr->GetTool<SELECTION_TOOL>();
const SELECTION& selection = selectionTool->GetSelection();
for( auto item : selection )
{
if( BOARD_CONNECTED_ITEM::ClassOf( item ) )
{
auto ci = static_cast<BOARD_CONNECTED_ITEM*>( item );
int item_net = ci->GetNetCode();
if( net < 0 )
{
net = item_net;
}
else if( net != item_net )
{
// more than one net selected: do nothing
return 0;
}
}
}
enableHighlight = ( net >= 0 && net != render->GetHighlightNetCode() );
}
// If we didn't get a net to highlight from the selection, use the cursor
if( net < 0 )
{
auto guide = frame->GetCollectorsGuide();
GENERAL_COLLECTOR collector;
// Find a connected item for which we are going to highlight a net
collector.Collect( board, GENERAL_COLLECTOR::PadsTracksOrZones,
wxPoint( aPosition.x, aPosition.y ), guide );
for( int i = 0; i < collector.GetCount(); i++ )
{
if( collector[i]->Type() == PCB_PAD_T )
{
frame->SendMessageToEESCHEMA( static_cast<BOARD_CONNECTED_ITEM*>( collector[i] ) );
break;
}
}
enableHighlight = ( collector.GetCount() > 0 );
// Obtain net code for the clicked item
if( enableHighlight )
net = static_cast<BOARD_CONNECTED_ITEM*>( collector[0] )->GetNetCode();
}
// Toggle highlight when the same net was picked
if( net > 0 && net == render->GetHighlightNetCode() )
enableHighlight = !render->IsHighlightEnabled();
if( enableHighlight != render->IsHighlightEnabled() || net != render->GetHighlightNetCode() )
{
render->SetHighlight( enableHighlight, net );
aToolMgr->GetView()->UpdateAllLayersColor();
}
// Store the highlighted netcode in the current board (for dialogs for instance)
if( enableHighlight && net >= 0 )
{
board->SetHighLightNet( net );
NETINFO_ITEM* netinfo = board->FindNet( net );
if( netinfo )
{
MSG_PANEL_ITEMS items;
netinfo->GetMsgPanelInfo( frame->GetUserUnits(), items );
frame->SetMsgPanel( items );
frame->SendCrossProbeNetName( netinfo->GetNetname() );
}
}
else
{
board->ResetHighLight();
frame->SetMsgPanel( board );
frame->SendCrossProbeNetName( "" );
}
return true;
}
int PCB_EDITOR_CONTROL::HighlightNet( const TOOL_EVENT& aEvent )
{
int netcode = aEvent.Parameter<intptr_t>();
if( netcode > 0 )
{
KIGFX::RENDER_SETTINGS* render = m_toolMgr->GetView()->GetPainter()->GetSettings();
render->SetHighlight( true, netcode );
m_toolMgr->GetView()->UpdateAllLayersColor();
}
else
{
// No net code specified, pick the net code belonging to the item under the cursor
highlightNet( m_toolMgr, getViewControls()->GetMousePosition() );
}
return 0;
}
int PCB_EDITOR_CONTROL::HighlightNetCursor( const TOOL_EVENT& aEvent )
{
// If the keyboard hotkey was triggered, the behavior is as follows:
// If we are already in the highlight tool, behave the same as a left click.
// If we are not, highlight the net of the selected item(s), or if there is
// no selection, then behave like a Ctrl+Left Click.
if( aEvent.IsAction( &PCB_ACTIONS::highlightNetSelection ) )
{
bool use_selection = ( m_frame->GetToolId() != ID_PCB_HIGHLIGHT_BUTT );
highlightNet( m_toolMgr, getViewControls()->GetMousePosition(),
use_selection );
}
Activate();
PICKER_TOOL* picker = m_toolMgr->GetTool<PICKER_TOOL>();
assert( picker );
m_frame->SetToolID( ID_PCB_HIGHLIGHT_BUTT, wxCURSOR_HAND, _( "Highlight net" ) );
picker->SetClickHandler( std::bind( highlightNet, m_toolMgr, _1, false ) );
picker->SetSnapping( false );
picker->Activate();
Wait();
return 0;
}
static bool showLocalRatsnest( TOOL_MANAGER* aToolMgr, BOARD* aBoard, const VECTOR2D& aPosition )
{
auto selectionTool = aToolMgr->GetTool<SELECTION_TOOL>();
auto modules = aBoard->Modules();
aToolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
aToolMgr->RunAction( PCB_ACTIONS::selectionCursor, true, EDIT_TOOL::FootprintFilter );
const SELECTION& selection = selectionTool->GetSelection();
if( selection.Empty() )
{
// Clear the previous local ratsnest if we click off all items
for( auto mod : modules )
{
for( auto pad : mod->Pads() )
{
pad->SetLocalRatsnestVisible( false );
}
}
return true;
}
for( auto item : selection )
{
if( item->Type() == PCB_MODULE_T )
{
for( auto pad : static_cast<MODULE *> (item)->Pads() )
{
pad->SetLocalRatsnestVisible( !pad->GetLocalRatsnestVisible() );
}
}
}
return true;
}
int PCB_EDITOR_CONTROL::ShowLocalRatsnest( const TOOL_EVENT& aEvent )
{
Activate();
auto picker = m_toolMgr->GetTool<PICKER_TOOL>();
auto board = getModel<BOARD>();
wxASSERT( picker );
wxASSERT( board );
m_frame->SetToolID( ID_PCB_SHOW_1_RATSNEST_BUTT, wxCURSOR_PENCIL, _( "Pick Components for Local Ratsnest" ) );
picker->SetClickHandler( std::bind( showLocalRatsnest, m_toolMgr, board, _1 ) );
picker->SetSnapping( false );
picker->Activate();
Wait();
return 0;
}
int PCB_EDITOR_CONTROL::UpdateSelectionRatsnest( const TOOL_EVENT& aEvent )
{
auto selectionTool = m_toolMgr->GetTool<SELECTION_TOOL>();
auto& selection = selectionTool->GetSelection();
auto connectivity = getModel<BOARD>()->GetConnectivity();
if( selection.Empty() )
{
connectivity->ClearDynamicRatsnest();
}
else if( m_slowRatsnest )
{
// Compute ratsnest only when user stops dragging for a moment
connectivity->HideDynamicRatsnest();
m_ratsnestTimer.Start( 20 );
}
else
{
// Check how much time doest it take to calculate ratsnest
PROF_COUNTER counter;
calculateSelectionRatsnest();
counter.Stop();
// If it is too slow, then switch to 'slow ratsnest' mode when
// ratsnest is calculated when user stops dragging items for a moment
if( counter.msecs() > 25 )
{
m_slowRatsnest = true;
connectivity->HideDynamicRatsnest();
}
}
return 0;
}
int PCB_EDITOR_CONTROL::HideSelectionRatsnest( const TOOL_EVENT& aEvent )
{
getModel<BOARD>()->GetConnectivity()->ClearDynamicRatsnest();
m_slowRatsnest = false;
return 0;
}
void PCB_EDITOR_CONTROL::ratsnestTimer( wxTimerEvent& aEvent )
{
m_ratsnestTimer.Stop();
calculateSelectionRatsnest();
static_cast<PCB_DRAW_PANEL_GAL*>( m_frame->GetGalCanvas() )->RedrawRatsnest();
m_frame->GetGalCanvas()->Refresh();
}
void PCB_EDITOR_CONTROL::calculateSelectionRatsnest()
{
auto selectionTool = m_toolMgr->GetTool<SELECTION_TOOL>();
auto& selection = selectionTool->GetSelection();
auto connectivity = board()->GetConnectivity();
std::vector<BOARD_ITEM*> items;
items.reserve( selection.Size() );
for( auto item : selection )
items.push_back( static_cast<BOARD_ITEM*>( item ) );
connectivity->ComputeDynamicRatsnest( items );
}
void PCB_EDITOR_CONTROL::setTransitions()
{
// Track & via size control
Go( &PCB_EDITOR_CONTROL::TrackWidthInc, PCB_ACTIONS::trackWidthInc.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::TrackWidthDec, PCB_ACTIONS::trackWidthDec.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::ViaSizeInc, PCB_ACTIONS::viaSizeInc.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::ViaSizeDec, PCB_ACTIONS::viaSizeDec.MakeEvent() );
// Zone actions
Go( &PCB_EDITOR_CONTROL::ZoneMerge, PCB_ACTIONS::zoneMerge.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::ZoneDuplicate, PCB_ACTIONS::zoneDuplicate.MakeEvent() );
// Placing tools
Go( &PCB_EDITOR_CONTROL::PlaceTarget, PCB_ACTIONS::placeTarget.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::PlaceModule, PCB_ACTIONS::placeModule.MakeEvent() );
// Other
Go( &PCB_EDITOR_CONTROL::ToggleLockSelected, PCB_ACTIONS::toggleLock.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::LockSelected, PCB_ACTIONS::lock.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::UnlockSelected, PCB_ACTIONS::unlock.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::CrossProbePcbToSch, SELECTION_TOOL::SelectedEvent );
Go( &PCB_EDITOR_CONTROL::CrossProbeSchToPcb, PCB_ACTIONS::crossProbeSchToPcb.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::DrillOrigin, PCB_ACTIONS::drillOrigin.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::HighlightNet, PCB_ACTIONS::highlightNet.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::HighlightNetCursor, PCB_ACTIONS::highlightNetCursor.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::HighlightNetCursor, PCB_ACTIONS::highlightNetSelection.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::ShowLocalRatsnest, PCB_ACTIONS::showLocalRatsnest.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::UpdateSelectionRatsnest, PCB_ACTIONS::selectionModified.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::HideSelectionRatsnest, SELECTION_TOOL::ClearedEvent );
}
const int PCB_EDITOR_CONTROL::WIDTH_STEP = 100000;