kicad/pcbnew/tools/drawing_tool.cpp

2968 lines
98 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2014-2017 CERN
* Copyright (C) 2018-2022 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 "drawing_tool.h"
#include "geometry/shape_rect.h"
#include <pgm_base.h>
#include <settings/settings_manager.h>
#include <pcbnew_settings.h>
#include <footprint_editor_settings.h>
#include <dialogs/dialog_text_properties.h>
#include <dialogs/dialog_track_via_size.h>
#include <geometry/geometry_utils.h>
#include <geometry/shape_segment.h>
#include <import_gfx/dialog_import_gfx.h>
#include <preview_items/two_point_assistant.h>
#include <preview_items/two_point_geom_manager.h>
#include <ratsnest/ratsnest_data.h>
#include <router/router_tool.h>
#include <tool/tool_manager.h>
#include <tools/pcb_actions.h>
#include <tools/pcb_grid_helper.h>
#include <tools/pcb_selection_tool.h>
#include <tools/tool_event_utils.h>
#include <tools/zone_create_helper.h>
#include <view/view.h>
#include <widgets/appearance_controls.h>
#include <widgets/infobar.h>
#include <bitmaps.h>
#include <board.h>
#include <board_commit.h>
#include <board_design_settings.h>
#include <confirm.h>
#include <footprint.h>
#include <fp_shape.h>
#include <fp_textbox.h>
#include <macros.h>
#include <painter.h>
#include <pcb_edit_frame.h>
#include <pcb_group.h>
#include <pcb_text.h>
#include <pcb_textbox.h>
#include <pcb_dimension.h>
#include <pcbnew_id.h>
#include <preview_items/arc_assistant.h>
#include <scoped_set_reset.h>
#include <status_popup.h>
#include <string_utils.h>
#include <zone.h>
using SCOPED_DRAW_MODE = SCOPED_SET_RESET<DRAWING_TOOL::MODE>;
class VIA_SIZE_MENU : public ACTION_MENU
{
public:
VIA_SIZE_MENU() :
ACTION_MENU( true )
{
SetIcon( BITMAPS::width_track_via );
SetTitle( _( "Select Via Size" ) );
}
protected:
ACTION_MENU* create() const override
{
return new VIA_SIZE_MENU();
}
void update() override
{
PCB_EDIT_FRAME* frame = (PCB_EDIT_FRAME*) getToolManager()->GetToolHolder();
EDA_UNITS units = frame->GetUserUnits();
BOARD_DESIGN_SETTINGS& bds = frame->GetBoard()->GetDesignSettings();
bool useIndex = !bds.m_UseConnectedTrackWidth
&& !bds.UseCustomTrackViaSize();
wxString msg;
Clear();
Append( ID_POPUP_PCB_SELECT_CUSTOM_WIDTH, _( "Use Custom Values..." ),
_( "Specify custom track and via sizes" ), wxITEM_CHECK );
Check( ID_POPUP_PCB_SELECT_CUSTOM_WIDTH, bds.UseCustomTrackViaSize() );
AppendSeparator();
for( unsigned i = 1; i < bds.m_ViasDimensionsList.size(); i++ )
{
VIA_DIMENSION via = bds.m_ViasDimensionsList[i];
if( via.m_Drill > 0 )
{
msg.Printf( _("Via %s, hole %s" ),
MessageTextFromValue( units, via.m_Diameter ),
MessageTextFromValue( units, via.m_Drill ) );
}
else
{
msg.Printf( _( "Via %s" ), MessageTextFromValue( units, via.m_Diameter ) );
}
int menuIdx = ID_POPUP_PCB_SELECT_VIASIZE1 + i;
Append( menuIdx, msg, wxEmptyString, wxITEM_CHECK );
Check( menuIdx, useIndex && bds.GetViaSizeIndex() == i );
}
}
OPT_TOOL_EVENT eventHandler( const wxMenuEvent& aEvent ) override
{
PCB_EDIT_FRAME* frame = (PCB_EDIT_FRAME*) getToolManager()->GetToolHolder();
BOARD_DESIGN_SETTINGS& bds = frame->GetBoard()->GetDesignSettings();
int id = aEvent.GetId();
// On Windows, this handler can be called with an event ID not existing in any
// menuitem, so only set flags when we have an ID match.
if( id == ID_POPUP_PCB_SELECT_CUSTOM_WIDTH )
{
DIALOG_TRACK_VIA_SIZE sizeDlg( frame, bds );
if( sizeDlg.ShowModal() == wxID_OK )
{
bds.UseCustomTrackViaSize( true );
bds.m_UseConnectedTrackWidth = false;
}
}
else if( id >= ID_POPUP_PCB_SELECT_VIASIZE1 && id <= ID_POPUP_PCB_SELECT_VIASIZE16 )
{
bds.UseCustomTrackViaSize( false );
bds.m_UseConnectedTrackWidth = false;
bds.SetViaSizeIndex( id - ID_POPUP_PCB_SELECT_VIASIZE1 );
}
return OPT_TOOL_EVENT( PCB_ACTIONS::trackViaSizeChanged.MakeEvent() );
}
};
DRAWING_TOOL::DRAWING_TOOL() :
PCB_TOOL_BASE( "pcbnew.InteractiveDrawing" ),
m_view( nullptr ),
m_controls( nullptr ),
m_board( nullptr ),
m_frame( nullptr ),
m_mode( MODE::NONE ),
m_inDrawingTool( false ),
m_layer( UNDEFINED_LAYER ),
m_stroke( 1, PLOT_DASH_TYPE::DEFAULT, COLOR4D::UNSPECIFIED )
{
}
DRAWING_TOOL::~DRAWING_TOOL()
{
}
bool DRAWING_TOOL::Init()
{
auto activeToolFunctor = [this]( const SELECTION& aSel )
{
return m_mode != MODE::NONE;
};
// some interactive drawing tools can undo the last point
auto canUndoPoint = [this]( const SELECTION& aSel )
{
return ( m_mode == MODE::ARC
|| m_mode == MODE::ZONE
|| m_mode == MODE::KEEPOUT
|| m_mode == MODE::GRAPHIC_POLYGON );
};
// functor for tools that can automatically close the outline
auto canCloseOutline = [this]( const SELECTION& aSel )
{
return ( m_mode == MODE::ZONE
|| m_mode == MODE::KEEPOUT
|| m_mode == MODE::GRAPHIC_POLYGON );
};
auto viaToolActive = [this]( const SELECTION& aSel )
{
return m_mode == MODE::VIA;
};
auto& ctxMenu = m_menu.GetMenu();
// cancel current tool goes in main context menu at the top if present
ctxMenu.AddItem( ACTIONS::cancelInteractive, activeToolFunctor, 1 );
ctxMenu.AddSeparator( 1 );
// tool-specific actions
ctxMenu.AddItem( PCB_ACTIONS::closeOutline, canCloseOutline, 200 );
ctxMenu.AddItem( PCB_ACTIONS::deleteLastPoint, canUndoPoint, 200 );
ctxMenu.AddCheckItem( PCB_ACTIONS::toggleHV45Mode, SELECTION_CONDITIONS::ShowAlways, 250 );
ctxMenu.AddSeparator( 500 );
std::shared_ptr<VIA_SIZE_MENU> viaSizeMenu = std::make_shared<VIA_SIZE_MENU>();
viaSizeMenu->SetTool( this );
m_menu.AddSubMenu( viaSizeMenu );
ctxMenu.AddMenu( viaSizeMenu.get(), viaToolActive, 500 );
ctxMenu.AddSeparator( 500 );
// Type-specific sub-menus will be added for us by other tools
// For example, zone fill/unfill is provided by the PCB control tool
// Finally, add the standard zoom/grid items
getEditFrame<PCB_BASE_FRAME>()->AddStandardSubMenus( m_menu );
return true;
}
void DRAWING_TOOL::Reset( RESET_REASON aReason )
{
// Init variables used by every drawing tool
m_view = getView();
m_controls = getViewControls();
m_board = getModel<BOARD>();
m_frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
updateStatusBar();
}
DRAWING_TOOL::MODE DRAWING_TOOL::GetDrawingMode() const
{
return m_mode;
}
void DRAWING_TOOL::updateStatusBar() const
{
if( m_frame )
{
SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
bool constrained;
if( m_frame->IsType( FRAME_PCB_EDITOR ) )
constrained = mgr.GetAppSettings<PCBNEW_SETTINGS>()->m_Use45DegreeLimit;
else
constrained = mgr.GetAppSettings<FOOTPRINT_EDITOR_SETTINGS>()->m_Use45Limit;
m_frame->DisplayConstraintsMsg(
constrained ? _( "Constrain to H, V, 45" ) : wxString( "" ) );
}
}
int DRAWING_TOOL::DrawLine( const TOOL_EVENT& aEvent )
{
if( m_isFootprintEditor && !m_frame->GetModel() )
return 0;
if( m_inDrawingTool )
return 0;
REENTRANCY_GUARD guard( &m_inDrawingTool );
FOOTPRINT* parentFootprint = dynamic_cast<FOOTPRINT*>( m_frame->GetModel() );
PCB_SHAPE* line = m_isFootprintEditor ? new FP_SHAPE( parentFootprint ) : new PCB_SHAPE;
BOARD_COMMIT commit( m_frame );
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::LINE );
OPT<VECTOR2D> startingPoint = boost::make_optional<VECTOR2D>( false, VECTOR2D( 0, 0 ) );
line->SetShape( SHAPE_T::SEGMENT );
line->SetFlags( IS_NEW );
if( aEvent.HasPosition() )
startingPoint = getViewControls()->GetCursorPosition( !aEvent.DisableGridSnapping() );
std::string tool = aEvent.GetCommandStr().get();
m_frame->PushTool( tool );
Activate();
while( drawSegment( tool, &line, startingPoint ) )
{
if( line )
{
if( m_isFootprintEditor )
static_cast<FP_SHAPE*>( line )->SetLocalCoord();
commit.Add( line );
commit.Push( _( "Draw a line segment" ) );
startingPoint = VECTOR2D( line->GetEnd() );
}
else
{
startingPoint = NULLOPT;
}
line = m_isFootprintEditor ? new FP_SHAPE( parentFootprint ) : new PCB_SHAPE;
line->SetShape( SHAPE_T::SEGMENT );
line->SetFlags( IS_NEW );
}
return 0;
}
int DRAWING_TOOL::DrawRectangle( const TOOL_EVENT& aEvent )
{
if( m_isFootprintEditor && !m_frame->GetModel() )
return 0;
if( m_inDrawingTool )
return 0;
REENTRANCY_GUARD guard( &m_inDrawingTool );
bool isTextBox = aEvent.IsAction( &PCB_ACTIONS::drawTextBox );
PCB_SHAPE* rect = nullptr;
BOARD_COMMIT commit( m_frame );
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::RECTANGLE );
OPT<VECTOR2D> startingPoint = boost::make_optional<VECTOR2D>( false, VECTOR2D( 0, 0 ) );
auto makeNew =
[&]() -> PCB_SHAPE*
{
if( m_isFootprintEditor )
{
FOOTPRINT* parentFootprint = dynamic_cast<FOOTPRINT*>( m_frame->GetModel() );
if( isTextBox )
return new FP_TEXTBOX( parentFootprint );
else
return new FP_SHAPE( parentFootprint );
}
else
{
if( isTextBox )
return new PCB_TEXTBOX( m_frame->GetModel() );
else
return new PCB_SHAPE();
}
};
rect = makeNew();
rect->SetShape( SHAPE_T::RECT );
rect->SetFilled( false );
rect->SetFlags(IS_NEW );
if( aEvent.HasPosition() )
startingPoint = getViewControls()->GetCursorPosition( !aEvent.DisableGridSnapping() );
std::string tool = aEvent.GetCommandStr().get();
m_frame->PushTool( tool );
Activate();
while( drawSegment( tool, &rect, startingPoint ) )
{
if( rect )
{
if( m_isFootprintEditor )
static_cast<FP_SHAPE*>( rect )->SetLocalCoord();
if( isTextBox && m_frame->ShowTextBoxPropertiesDialog( rect ) != wxID_OK )
{
delete rect;
rect = nullptr;
}
else
{
commit.Add( rect );
commit.Push( isTextBox ? _( "Draw a text box" ) : _( "Draw a rectangle" ) );
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, rect );
}
}
rect = makeNew();
rect->SetShape( SHAPE_T::RECT );
rect->SetFilled( false );
rect->SetFlags(IS_NEW );
startingPoint = NULLOPT;
}
return 0;
}
int DRAWING_TOOL::DrawCircle( const TOOL_EVENT& aEvent )
{
if( m_isFootprintEditor && !m_frame->GetModel() )
return 0;
if( m_inDrawingTool )
return 0;
REENTRANCY_GUARD guard( &m_inDrawingTool );
FOOTPRINT* parentFootprint = dynamic_cast<FOOTPRINT*>( m_frame->GetModel() );
PCB_SHAPE* circle = m_isFootprintEditor ? new FP_SHAPE( parentFootprint ) : new PCB_SHAPE;
BOARD_COMMIT commit( m_frame );
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::CIRCLE );
OPT<VECTOR2D> startingPoint = boost::make_optional<VECTOR2D>( false, VECTOR2D( 0, 0 ) );
circle->SetShape( SHAPE_T::CIRCLE );
circle->SetFilled( false );
circle->SetFlags( IS_NEW );
if( aEvent.HasPosition() )
startingPoint = getViewControls()->GetCursorPosition( !aEvent.DisableGridSnapping() );
std::string tool = aEvent.GetCommandStr().get();
m_frame->PushTool( tool );
Activate();
while( drawSegment( tool, &circle, startingPoint ) )
{
if( circle )
{
if( m_isFootprintEditor )
static_cast<FP_SHAPE*>( circle )->SetLocalCoord();
commit.Add( circle );
commit.Push( _( "Draw a circle" ) );
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, circle );
}
circle = m_isFootprintEditor ? new FP_SHAPE( parentFootprint ) : new PCB_SHAPE;
circle->SetShape( SHAPE_T::CIRCLE );
circle->SetFilled( false );
circle->SetFlags( IS_NEW );
startingPoint = NULLOPT;
}
return 0;
}
int DRAWING_TOOL::DrawArc( const TOOL_EVENT& aEvent )
{
if( m_isFootprintEditor && !m_frame->GetModel() )
return 0;
if( m_inDrawingTool )
return 0;
REENTRANCY_GUARD guard( &m_inDrawingTool );
FOOTPRINT* parentFootprint = dynamic_cast<FOOTPRINT*>( m_frame->GetModel() );
PCB_SHAPE* arc = m_isFootprintEditor ? new FP_SHAPE( parentFootprint ) : new PCB_SHAPE;
BOARD_COMMIT commit( m_frame );
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::ARC );
OPT<VECTOR2D> startingPoint = boost::make_optional<VECTOR2D>( false, { 0, 0 } );
arc->SetShape( SHAPE_T::ARC );
arc->SetFlags( IS_NEW );
std::string tool = aEvent.GetCommandStr().get();
m_frame->PushTool( tool );
Activate();
if( aEvent.HasPosition() )
startingPoint = aEvent.Position();
while( drawArc( tool, &arc, startingPoint ) )
{
if( arc )
{
if( m_isFootprintEditor )
static_cast<FP_SHAPE*>( arc )->SetLocalCoord();
commit.Add( arc );
commit.Push( _( "Draw an arc" ) );
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, arc );
}
arc = m_isFootprintEditor ? new FP_SHAPE( parentFootprint ) : new PCB_SHAPE;
arc->SetShape( SHAPE_T::ARC );
arc->SetFlags( IS_NEW );
startingPoint = NULLOPT;
}
return 0;
}
int DRAWING_TOOL::PlaceText( const TOOL_EVENT& aEvent )
{
if( m_isFootprintEditor && !m_frame->GetModel() )
return 0;
if( m_inDrawingTool )
return 0;
REENTRANCY_GUARD guard( &m_inDrawingTool );
COMMON_SETTINGS* common_settings = Pgm().GetCommonSettings();
BOARD_ITEM* text = nullptr;
bool ignorePrimePosition = false;
const BOARD_DESIGN_SETTINGS& dsnSettings = m_frame->GetDesignSettings();
BOARD_COMMIT commit( m_frame );
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::TEXT );
auto cleanup =
[&]()
{
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
m_controls->ForceCursorPosition( false );
m_controls->ShowCursor( true );
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
delete text;
text = nullptr;
};
auto setCursor =
[&]()
{
if( text )
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
else
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::TEXT );
};
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
std::string tool = aEvent.GetCommandStr().get();
m_frame->PushTool( tool );
Activate();
// Must be done after Activate() so that it gets set into the correct context
m_controls->ShowCursor( true );
m_controls->ForceCursorPosition( false );
// do not capture or auto-pan until we start placing some text
// Set initial cursor
setCursor();
if( aEvent.HasPosition() )
{
m_toolMgr->PrimeTool( aEvent.Position() );
}
else if( common_settings->m_Input.immediate_actions && !aEvent.IsReactivate() )
{
m_toolMgr->PrimeTool( { 0, 0 } );
ignorePrimePosition = true;
}
// Main loop: keep receiving events
while( TOOL_EVENT* evt = Wait() )
{
setCursor();
VECTOR2I cursorPos = m_controls->GetCursorPosition();
if( evt->IsCancelInteractive() )
{
if( text )
{
cleanup();
}
else
{
m_frame->PopTool( tool );
break;
}
}
else if( evt->IsActivate() )
{
if( text )
cleanup();
if( evt->IsMoveTool() )
{
// leave ourselves on the stack so we come back after the move
break;
}
else
{
m_frame->PopTool( tool );
break;
}
}
else if( evt->IsClick( BUT_RIGHT ) )
{
m_menu.ShowContextMenu( selection() );
}
else if( evt->IsClick( BUT_LEFT ) )
{
bool placing = text != nullptr;
if( !text )
{
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
m_controls->ForceCursorPosition( true, m_controls->GetCursorPosition() );
PCB_LAYER_ID layer = m_frame->GetActiveLayer();
wxSize textSize = dsnSettings.GetTextSize( layer );
int thickness = dsnSettings.GetTextThickness( layer );
// Init the new item attributes
if( m_isFootprintEditor )
{
FP_TEXT* fpText = new FP_TEXT( (FOOTPRINT*) m_frame->GetModel() );
fpText->SetLayer( layer );
fpText->SetTextSize( textSize );
fpText->SetTextThickness( thickness );
fpText->SetBold( abs( thickness - GetPenSizeForBold( textSize ) ) <
abs( thickness - GetPenSizeForNormal( textSize ) ) );
fpText->SetItalic( dsnSettings.GetTextItalic( layer ) );
fpText->SetKeepUpright( dsnSettings.GetTextUpright( layer ) );
fpText->SetTextPos( cursorPos );
text = fpText;
DIALOG_TEXT_PROPERTIES textDialog( m_frame, fpText );
bool cancelled;
RunMainStack( [&]()
{
cancelled = !textDialog.ShowModal();
} );
if( cancelled || NoPrintableChars( fpText->GetText() ) )
{
delete text;
text = nullptr;
}
else if( fpText->GetTextPos() != cursorPos )
{
// If the user modified the location then go ahead and place it there.
// Otherwise we'll drag.
placing = true;
}
}
else
{
PCB_TEXT* pcbText = new PCB_TEXT( m_frame->GetModel() );
pcbText->SetFlags( IS_NEW );
pcbText->SetLayer( layer );
// Set the mirrored option for layers on the BACK side of the board
if( IsBackLayer( layer ) )
pcbText->SetMirrored( true );
pcbText->SetTextSize( textSize );
pcbText->SetTextThickness( thickness );
pcbText->SetBold( abs( thickness - GetPenSizeForBold( textSize ) ) <
abs( thickness - GetPenSizeForNormal( textSize ) ) );
pcbText->SetItalic( dsnSettings.GetTextItalic( layer ) );
pcbText->SetTextPos( cursorPos );
RunMainStack( [&]()
{
m_frame->ShowTextPropertiesDialog( pcbText );
} );
if( NoPrintableChars( pcbText->GetText() ) )
delete pcbText;
else
text = pcbText;
}
if( text )
{
if( !m_view->IsLayerVisible( text->GetLayer() ) )
{
m_frame->GetAppearancePanel()->SetLayerVisible( text->GetLayer(), true );
m_frame->GetCanvas()->Refresh();
}
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, text );
m_view->Update( &selection() );
// update the cursor so it looks correct before another event
setCursor();
}
}
if( placing )
{
text->ClearFlags();
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
commit.Add( text );
commit.Push( _( "Place a text" ) );
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, text );
text = nullptr;
}
m_controls->ForceCursorPosition( false );
// If we started with a hotkey which has a position then warp back to that.
// Otherwise update to the current mouse position pinned inside the autoscroll
// boundaries.
if( evt->IsPrime() && !ignorePrimePosition )
{
cursorPos = evt->Position();
m_controls->WarpMouseCursor( cursorPos, true );
}
else
{
m_controls->PinCursorInsideNonAutoscrollArea( true );
cursorPos = m_controls->GetMousePosition();
}
m_toolMgr->RunAction( PCB_ACTIONS::refreshPreview );
m_controls->ShowCursor( true );
m_controls->CaptureCursor( text != nullptr );
m_controls->SetAutoPan( text != nullptr );
}
else if( text && ( evt->IsMotion() || evt->IsAction( &PCB_ACTIONS::refreshPreview ) ) )
{
text->SetPosition( cursorPos );
selection().SetReferencePoint( cursorPos );
m_view->Update( &selection() );
}
else if( evt->IsAction( &PCB_ACTIONS::properties ) )
{
if( text )
{
frame()->OnEditItemRequest( text );
m_view->Update( &selection() );
frame()->SetMsgPanel( text );
}
else
{
evt->SetPassEvent();
}
}
else
{
evt->SetPassEvent();
}
}
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
m_controls->ForceCursorPosition( false );
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
if( selection().Empty() )
m_frame->SetMsgPanel( board() );
return 0;
}
void DRAWING_TOOL::constrainDimension( PCB_DIMENSION_BASE* aDim )
{
const VECTOR2I lineVector{ aDim->GetEnd() - aDim->GetStart() };
aDim->SetEnd( aDim->GetStart() + GetVectorSnapped45( lineVector ) );
aDim->Update();
}
int DRAWING_TOOL::DrawDimension( const TOOL_EVENT& aEvent )
{
if( m_isFootprintEditor && !m_frame->GetModel() )
return 0;
if( m_inDrawingTool )
return 0;
REENTRANCY_GUARD guard( &m_inDrawingTool );
enum DIMENSION_STEPS
{
SET_ORIGIN = 0,
SET_END,
SET_HEIGHT,
FINISHED
};
TOOL_EVENT originalEvent = aEvent;
PCB_DIMENSION_BASE* dimension = nullptr;
BOARD_COMMIT commit( m_frame );
PCB_GRID_HELPER grid( m_toolMgr, m_frame->GetMagneticItemsSettings() );
BOARD_DESIGN_SETTINGS& boardSettings = m_board->GetDesignSettings();
PCB_SELECTION preview; // A VIEW_GROUP that serves as a preview for the new item(s)
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::DIMENSION );
int step = SET_ORIGIN;
KICAD_T t = PCB_DIMENSION_T;
m_view->Add( &preview );
auto cleanup =
[&]()
{
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
m_controls->ForceCursorPosition( false );
preview.Clear();
m_view->Update( &preview );
delete dimension;
dimension = nullptr;
step = SET_ORIGIN;
};
auto setCursor =
[&]()
{
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MEASURE );
};
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
std::string tool = aEvent.GetCommandStr().get();
m_frame->PushTool( tool );
Activate();
// Must be done after Activate() so that it gets set into the correct context
m_controls->ShowCursor( true );
m_controls->ForceCursorPosition( false );
// Set initial cursor
setCursor();
m_toolMgr->RunAction( ACTIONS::refreshPreview );
if( aEvent.HasPosition() )
m_toolMgr->PrimeTool( aEvent.Position() );
// Main loop: keep receiving events
while( TOOL_EVENT* evt = Wait() )
{
if( step > SET_ORIGIN )
frame()->SetMsgPanel( dimension );
setCursor();
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
if( step == SET_HEIGHT )
{
if( dimension->GetStart().x != dimension->GetEnd().x
&& dimension->GetStart().y != dimension->GetEnd().y )
{
// Not cardinal. Grid snapping doesn't make sense for height.
grid.SetUseGrid( false );
}
}
VECTOR2I cursorPos = evt->HasPosition() ? evt->Position() : m_controls->GetMousePosition();
cursorPos = grid.BestSnapAnchor( cursorPos, nullptr );
m_controls->ForceCursorPosition( true, cursorPos );
if( evt->IsCancelInteractive() )
{
m_controls->SetAutoPan( false );
if( step != SET_ORIGIN ) // start from the beginning
{
cleanup();
}
else
{
m_frame->PopTool( tool );
break;
}
}
else if( evt->IsActivate() )
{
if( step != SET_ORIGIN )
cleanup();
if( evt->IsPointEditor() )
{
// don't exit (the point editor runs in the background)
}
else if( evt->IsMoveTool() )
{
// leave ourselves on the stack so we come back after the move
break;
}
else
{
m_frame->PopTool( tool );
break;
}
}
else if( evt->IsAction( &PCB_ACTIONS::incWidth ) && step != SET_ORIGIN )
{
m_stroke.SetWidth( m_stroke.GetWidth() + WIDTH_STEP );
dimension->SetLineThickness( m_stroke.GetWidth() );
m_view->Update( &preview );
frame()->SetMsgPanel( dimension );
}
else if( evt->IsAction( &PCB_ACTIONS::decWidth ) && step != SET_ORIGIN )
{
if( (unsigned) m_stroke.GetWidth() > WIDTH_STEP )
{
m_stroke.SetWidth( m_stroke.GetWidth() - WIDTH_STEP );
dimension->SetLineThickness( m_stroke.GetWidth() );
m_view->Update( &preview );
frame()->SetMsgPanel( dimension );
}
}
else if( evt->IsClick( BUT_RIGHT ) )
{
m_menu.ShowContextMenu( selection() );
}
else if( evt->IsClick( BUT_LEFT ) )
{
switch( step )
{
case SET_ORIGIN:
{
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
PCB_LAYER_ID layer = m_frame->GetActiveLayer();
// Init the new item attributes
auto setMeasurementAttributes =
[&]( PCB_DIMENSION_BASE* aDim )
{
aDim->SetUnitsMode( boardSettings.m_DimensionUnitsMode );
aDim->SetUnitsFormat( boardSettings.m_DimensionUnitsFormat );
aDim->SetPrecision( boardSettings.m_DimensionPrecision );
aDim->SetSuppressZeroes( boardSettings.m_DimensionSuppressZeroes );
aDim->SetTextPositionMode( boardSettings.m_DimensionTextPosition );
aDim->SetKeepTextAligned( boardSettings.m_DimensionKeepTextAligned );
if( boardSettings.m_DimensionUnitsMode == DIM_UNITS_MODE::AUTOMATIC )
aDim->SetUnits( m_frame->GetUserUnits() );
};
if( originalEvent.IsAction( &PCB_ACTIONS::drawAlignedDimension ) )
{
dimension = new PCB_DIM_ALIGNED( m_frame->GetModel(),
m_isFootprintEditor ? PCB_FP_DIM_ALIGNED_T
: PCB_DIM_ALIGNED_T );
setMeasurementAttributes( dimension );
}
else if( originalEvent.IsAction( &PCB_ACTIONS::drawOrthogonalDimension ) )
{
dimension = new PCB_DIM_ORTHOGONAL( m_frame->GetModel(), m_isFootprintEditor );
setMeasurementAttributes( dimension );
}
else if( originalEvent.IsAction( &PCB_ACTIONS::drawCenterDimension ) )
{
dimension = new PCB_DIM_CENTER( m_frame->GetModel(), m_isFootprintEditor );
}
else if( originalEvent.IsAction( &PCB_ACTIONS::drawRadialDimension ) )
{
dimension = new PCB_DIM_RADIAL( m_frame->GetModel(), m_isFootprintEditor );
setMeasurementAttributes( dimension );
}
else if( originalEvent.IsAction( &PCB_ACTIONS::drawLeader ) )
{
dimension = new PCB_DIM_LEADER( m_frame->GetModel(), m_isFootprintEditor );
dimension->Text().SetPosition( cursorPos );
}
else
{
wxFAIL_MSG( wxT( "Unhandled action in DRAWING_TOOL::DrawDimension" ) );
}
t = dimension->Type();
dimension->SetLayer( layer );
dimension->Text().SetTextSize( boardSettings.GetTextSize( layer ) );
dimension->Text().SetTextThickness( boardSettings.GetTextThickness( layer ) );
dimension->Text().SetItalic( boardSettings.GetTextItalic( layer ) );
dimension->SetLineThickness( boardSettings.GetLineThickness( layer ) );
dimension->SetArrowLength( boardSettings.m_DimensionArrowLength );
dimension->SetExtensionOffset( boardSettings.m_DimensionExtensionOffset );
dimension->SetStart( cursorPos );
dimension->SetEnd( cursorPos );
dimension->Update();
if( !m_view->IsLayerVisible( layer ) )
{
m_frame->GetAppearancePanel()->SetLayerVisible( layer, true );
m_frame->GetCanvas()->Refresh();
}
preview.Add( dimension );
frame()->SetMsgPanel( dimension );
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( true );
break;
}
case SET_END:
// Dimensions that have origin and end in the same spot are not valid
if( dimension->GetStart() == dimension->GetEnd() )
{
--step;
break;
}
if( t == PCB_DIM_CENTER_T || t == PCB_DIM_RADIAL_T || t == PCB_DIM_LEADER_T
|| t == PCB_FP_DIM_CENTER_T || t == PCB_FP_DIM_RADIAL_T || t == PCB_FP_DIM_LEADER_T )
{
// No separate height step
++step;
KI_FALLTHROUGH;
}
else
{
break;
}
case SET_HEIGHT:
assert( dimension->GetStart() != dimension->GetEnd() );
assert( dimension->GetLineThickness() > 0 );
preview.Remove( dimension );
commit.Add( dimension );
commit.Push( _( "Draw a dimension" ) );
if( t == PCB_DIM_LEADER_T || t == PCB_FP_DIM_LEADER_T )
{
// Run the edit immediately to set the leader text
m_toolMgr->RunAction( PCB_ACTIONS::properties, true, dimension );
}
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, dimension );
break;
}
if( ++step == FINISHED )
{
step = SET_ORIGIN;
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
}
}
else if( evt->IsMotion() )
{
switch( step )
{
case SET_END:
dimension->SetEnd( cursorPos );
if( Is45Limited() || t == PCB_DIM_CENTER_T || t == PCB_FP_DIM_CENTER_T )
constrainDimension( dimension );
if( t == PCB_DIM_ORTHOGONAL_T || t == PCB_FP_DIM_ORTHOGONAL_T )
{
PCB_DIM_ORTHOGONAL* ortho = static_cast<PCB_DIM_ORTHOGONAL*>( dimension );
BOX2I bounds( dimension->GetStart(),
dimension->GetEnd() - dimension->GetStart() );
// Create a nice preview by measuring the longer dimension
bool vert = bounds.GetWidth() < bounds.GetHeight();
ortho->SetOrientation( vert ? PCB_DIM_ORTHOGONAL::DIR::VERTICAL
: PCB_DIM_ORTHOGONAL::DIR::HORIZONTAL );
}
else if( t == PCB_DIM_RADIAL_T || t == PCB_FP_DIM_RADIAL_T )
{
PCB_DIM_RADIAL* radialDim = static_cast<PCB_DIM_RADIAL*>( dimension );
VECTOR2I textOffset( radialDim->GetArrowLength() * 10, 0 );
if( radialDim->GetEnd().x < radialDim->GetStart().x )
textOffset = -textOffset;
radialDim->Text().SetPosition( radialDim->GetKnee() + textOffset );
}
else if( t == PCB_DIM_LEADER_T || t == PCB_FP_DIM_LEADER_T )
{
VECTOR2I textOffset( dimension->GetArrowLength() * 10, 0 );
if( dimension->GetEnd().x < dimension->GetStart().x )
textOffset = -textOffset;
dimension->Text().SetPosition( dimension->GetEnd() + textOffset );
}
dimension->Update();
break;
case SET_HEIGHT:
if( t == PCB_DIM_ALIGNED_T || t == PCB_FP_DIM_ALIGNED_T )
{
PCB_DIM_ALIGNED* aligned = static_cast<PCB_DIM_ALIGNED*>( dimension );
// Calculating the direction of travel perpendicular to the selected axis
double angle = aligned->GetAngle() + ( M_PI / 2 );
VECTOR2I delta( (VECTOR2I) cursorPos - dimension->GetEnd() );
double height = ( delta.x * cos( angle ) ) + ( delta.y * sin( angle ) );
aligned->SetHeight( height );
aligned->Update();
}
else if( t == PCB_DIM_ORTHOGONAL_T || t == PCB_FP_DIM_ORTHOGONAL_T )
{
PCB_DIM_ORTHOGONAL* ortho = static_cast<PCB_DIM_ORTHOGONAL*>( dimension );
BOX2I bbox( dimension->GetStart(),
dimension->GetEnd() - dimension->GetStart() );
VECTOR2I direction( cursorPos - bbox.Centre() );
bool vert;
// Only change the orientation when we move outside the bbox
if( !bbox.Contains( cursorPos ) )
{
// If the dimension is horizontal or vertical, set correct orientation
// otherwise, test if we're left/right of the bounding box or above/below it
if( bbox.GetWidth() == 0 )
vert = true;
else if( bbox.GetHeight() == 0 )
vert = false;
else if( cursorPos.x > bbox.GetLeft() && cursorPos.x < bbox.GetRight() )
vert = false;
else if( cursorPos.y > bbox.GetTop() && cursorPos.y < bbox.GetBottom() )
vert = true;
else
vert = std::abs( direction.y ) < std::abs( direction.x );
ortho->SetOrientation( vert ? PCB_DIM_ORTHOGONAL::DIR::VERTICAL
: PCB_DIM_ORTHOGONAL::DIR::HORIZONTAL );
}
else
{
vert = ortho->GetOrientation() == PCB_DIM_ORTHOGONAL::DIR::VERTICAL;
}
VECTOR2I heightVector( cursorPos - dimension->GetStart() );
ortho->SetHeight( vert ? heightVector.x : heightVector.y );
ortho->Update();
}
break;
}
// Show a preview of the item
m_view->Update( &preview );
}
else if( evt->IsAction( &PCB_ACTIONS::layerChanged ) )
{
if( dimension )
{
PCB_LAYER_ID layer = m_frame->GetActiveLayer();
if( !m_view->IsLayerVisible( layer ) )
{
m_frame->GetAppearancePanel()->SetLayerVisible( layer, true );
m_frame->GetCanvas()->Refresh();
}
dimension->SetLayer( layer );
dimension->Text().SetTextSize( boardSettings.GetTextSize( layer ) );
dimension->Text().SetTextThickness( boardSettings.GetTextThickness( layer ) );
dimension->Text().SetItalic( boardSettings.GetTextItalic( layer ) );
dimension->SetLineThickness( boardSettings.GetLineThickness( layer ) );
dimension->Update();
m_view->Update( &preview );
frame()->SetMsgPanel( dimension );
}
else
{
evt->SetPassEvent();
}
}
else if( evt->IsAction( &PCB_ACTIONS::properties ) )
{
if( step == SET_END || step == SET_HEIGHT )
{
frame()->OnEditItemRequest( dimension );
dimension->Update();
frame()->SetMsgPanel( dimension );
break;
}
else
{
evt->SetPassEvent();
}
}
else
{
evt->SetPassEvent();
}
}
if( step != SET_ORIGIN )
delete dimension;
m_controls->SetAutoPan( false );
m_controls->ForceCursorPosition( false );
m_controls->CaptureCursor( false );
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
m_view->Remove( &preview );
if( selection().Empty() )
m_frame->SetMsgPanel( board() );
return 0;
}
int DRAWING_TOOL::PlaceImportedGraphics( const TOOL_EVENT& aEvent )
{
if( !m_frame->GetModel() )
return 0;
if( m_inDrawingTool )
return 0;
REENTRANCY_GUARD guard( &m_inDrawingTool );
// Note: PlaceImportedGraphics() will convert PCB_SHAPE_T and PCB_TEXT_T to footprint
// items if needed
DIALOG_IMPORT_GFX dlg( m_frame, m_isFootprintEditor );
int dlgResult = dlg.ShowModal();
std::list<std::unique_ptr<EDA_ITEM>>& list = dlg.GetImportedItems();
if( dlgResult != wxID_OK )
return 0;
// Ensure the list is not empty:
if( list.empty() )
{
wxMessageBox( _( "No graphic items found in file.") );
return 0;
}
m_toolMgr->RunAction( ACTIONS::cancelInteractive, true );
std::vector<BOARD_ITEM*> newItems; // all new items, including group
std::vector<BOARD_ITEM*> selectedItems; // the group, or newItems if no group
PCB_SELECTION preview;
BOARD_COMMIT commit( m_frame );
PCB_GROUP* group = nullptr;
if( dlg.ShouldGroupItems() )
{
if( m_isFootprintEditor )
group = new PCB_GROUP( m_frame->GetBoard()->GetFirstFootprint() );
else
group = new PCB_GROUP( m_frame->GetBoard() );
newItems.push_back( group );
selectedItems.push_back( group );
preview.Add( group );
}
for( std::unique_ptr<EDA_ITEM>& ptr : list )
{
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( ptr.get() );
if( m_isFootprintEditor )
wxASSERT( item->Type() == PCB_FP_SHAPE_T || item->Type() == PCB_FP_TEXT_T );
else
wxASSERT( item->Type() == PCB_SHAPE_T || item->Type() == PCB_TEXT_T );
newItems.push_back( item );
if( group )
group->AddItem( item );
else
selectedItems.push_back( item );
preview.Add( item );
ptr.release();
}
if( !dlg.IsPlacementInteractive() )
{
for( BOARD_ITEM* item : newItems )
commit.Add( item );
commit.Push( _( "Place a DXF_SVG drawing" ) );
return 0;
}
m_view->Add( &preview );
// Clear the current selection then select the drawings so that edit tools work on them
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
m_toolMgr->RunAction( PCB_ACTIONS::selectItems, true, &selectedItems );
std::string tool = aEvent.GetCommandStr().get();
m_frame->PushTool( tool );
auto setCursor =
[&]()
{
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
};
Activate();
// Must be done after Activate() so that it gets set into the correct context
m_controls->ShowCursor( true );
m_controls->ForceCursorPosition( false );
// Set initial cursor
setCursor();
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::DXF );
// Now move the new items to the current cursor position:
VECTOR2I cursorPos = m_controls->GetCursorPosition();
VECTOR2I delta = cursorPos - static_cast<BOARD_ITEM*>( preview.Front() )->GetPosition();
for( BOARD_ITEM* item : selectedItems )
item->Move( delta );
m_view->Update( &preview );
// Main loop: keep receiving events
while( TOOL_EVENT* evt = Wait() )
{
setCursor();
cursorPos = m_controls->GetCursorPosition();
if( evt->IsCancelInteractive() || evt->IsActivate() )
{
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
for( BOARD_ITEM* item : newItems )
delete item;
break;
}
else if( evt->IsMotion() )
{
delta = cursorPos - static_cast<BOARD_ITEM*>( preview.Front() )->GetPosition();
for( BOARD_ITEM* item : selectedItems )
item->Move( delta );
m_view->Update( &preview );
}
else if( evt->IsClick( BUT_RIGHT ) )
{
m_menu.ShowContextMenu( selection() );
}
else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) )
{
// Place the imported drawings
for( BOARD_ITEM* item : newItems )
commit.Add( item );
commit.Push( _( "Place a DXF_SVG drawing" ) );
break; // This is a one-shot command, not a tool
}
else
{
evt->SetPassEvent();
}
}
preview.Clear();
m_view->Remove( &preview );
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
m_controls->ForceCursorPosition( false );
m_frame->PopTool( tool );
return 0;
}
int DRAWING_TOOL::SetAnchor( const TOOL_EVENT& aEvent )
{
wxASSERT( m_isFootprintEditor );
if( !m_frame->GetModel() )
return 0;
if( m_inDrawingTool )
return 0;
REENTRANCY_GUARD guard( &m_inDrawingTool );
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::ANCHOR );
PCB_GRID_HELPER grid( m_toolMgr, m_frame->GetMagneticItemsSettings() );
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
std::string tool = aEvent.GetCommandStr().get();
m_frame->PushTool( tool );
auto setCursor =
[&]()
{
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::BULLSEYE );
};
Activate();
// Must be done after Activate() so that it gets set into the correct context
m_controls->ShowCursor( true );
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( false );
m_controls->ForceCursorPosition( false );
// Set initial cursor
setCursor();
while( TOOL_EVENT* evt = Wait() )
{
setCursor();
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
VECTOR2I cursorPos = grid.BestSnapAnchor( m_controls->GetMousePosition(),
LSET::AllLayersMask() );
m_controls->ForceCursorPosition( true, cursorPos );
if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) )
{
FOOTPRINT* footprint = (FOOTPRINT*) m_frame->GetModel();
BOARD_COMMIT commit( m_frame );
commit.Modify( footprint );
// set the new relative internal local coordinates of footprint items
VECTOR2I moveVector = footprint->GetPosition() - cursorPos;
footprint->MoveAnchorPosition( moveVector );
commit.Push( _( "Move the footprint reference anchor" ) );
// Usually, we do not need to change twice the anchor position,
// so deselect the active tool
m_frame->PopTool( tool );
break;
}
else if( evt->IsClick( BUT_RIGHT ) )
{
m_menu.ShowContextMenu( selection() );
}
else if( evt->IsCancelInteractive() || evt->IsActivate() )
{
m_frame->PopTool( tool );
break;
}
else
{
evt->SetPassEvent();
}
}
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
m_controls->ForceCursorPosition( false );
return 0;
}
int DRAWING_TOOL::ToggleHV45Mode( const TOOL_EVENT& toolEvent )
{
#define TOGGLE( a ) a = !a
SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
if( frame()->IsType( FRAME_PCB_EDITOR ) )
TOGGLE( mgr.GetAppSettings<PCBNEW_SETTINGS>()->m_Use45DegreeLimit );
else
TOGGLE( mgr.GetAppSettings<FOOTPRINT_EDITOR_SETTINGS>()->m_Use45Limit );
updateStatusBar();
return 0;
#undef TOGGLE
}
/**
* Update a #PCB_SHAPE from the current state of a #TWO_POINT_GEOMETRY_MANAGER.
*/
static void updateSegmentFromGeometryMgr( const KIGFX::PREVIEW::TWO_POINT_GEOMETRY_MANAGER& aMgr,
PCB_SHAPE* aGraphic )
{
if( !aMgr.IsReset() )
{
aGraphic->SetStart( aMgr.GetOrigin() );
aGraphic->SetEnd( aMgr.GetEnd() );
}
}
bool DRAWING_TOOL::drawSegment( const std::string& aTool, PCB_SHAPE** aGraphic,
OPT<VECTOR2D> aStartingPoint )
{
SHAPE_T shape = ( *aGraphic )->GetShape();
// Only three shapes are currently supported
wxASSERT( shape == SHAPE_T::SEGMENT || shape == SHAPE_T::CIRCLE || shape == SHAPE_T::RECT );
EDA_UNITS userUnits = m_frame->GetUserUnits();
PCB_GRID_HELPER grid( m_toolMgr, m_frame->GetMagneticItemsSettings() );
PCB_SHAPE*& graphic = *aGraphic;
if( m_layer != m_frame->GetActiveLayer() )
{
m_layer = m_frame->GetActiveLayer();
m_stroke.SetWidth( getSegmentWidth( m_layer ) );
m_stroke.SetPlotStyle( PLOT_DASH_TYPE::DEFAULT );
m_stroke.SetColor( COLOR4D::UNSPECIFIED );
}
// geometric construction manager
KIGFX::PREVIEW::TWO_POINT_GEOMETRY_MANAGER twoPointManager;
// drawing assistant overlay
// TODO: workaround because EDA_SHAPE_TYPE_T is not visible from commons.
KIGFX::PREVIEW::GEOM_SHAPE geomShape( static_cast<KIGFX::PREVIEW::GEOM_SHAPE>( shape ) );
KIGFX::PREVIEW::TWO_POINT_ASSISTANT twoPointAsst( twoPointManager, userUnits, geomShape );
// Add a VIEW_GROUP that serves as a preview for the new item
PCB_SELECTION preview;
m_view->Add( &preview );
m_view->Add( &twoPointAsst );
bool started = false;
bool cancelled = false;
bool isLocalOriginSet = ( m_frame->GetScreen()->m_LocalOrigin != VECTOR2D( 0, 0 ) );
VECTOR2I cursorPos = m_controls->GetMousePosition();
auto setCursor =
[&]()
{
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
};
auto cleanup =
[&]()
{
preview.Clear();
m_view->Update( &preview );
delete graphic;
graphic = nullptr;
if( !isLocalOriginSet )
m_frame->GetScreen()->m_LocalOrigin = VECTOR2D( 0, 0 );
};
m_controls->ShowCursor( true );
m_controls->ForceCursorPosition( false );
// Set initial cursor
setCursor();
m_toolMgr->RunAction( ACTIONS::refreshPreview );
if( aStartingPoint )
m_toolMgr->PrimeTool( aStartingPoint.get() );
// Main loop: keep receiving events
while( TOOL_EVENT* evt = Wait() )
{
setCursor();
if( started )
m_frame->SetMsgPanel( graphic );
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
cursorPos = grid.BestSnapAnchor( m_controls->GetMousePosition(), m_layer );
m_controls->ForceCursorPosition( true, cursorPos );
if( evt->IsCancelInteractive() )
{
cleanup();
if( !started )
{
// We've handled the cancel event. Don't cancel other tools
evt->SetPassEvent( false );
m_frame->PopTool( aTool );
cancelled = true;
}
break;
}
else if( evt->IsActivate() )
{
if( evt->IsPointEditor() )
{
// don't exit (the point editor runs in the background)
}
else if( evt->IsMoveTool() )
{
cleanup();
// leave ourselves on the stack so we come back after the move
cancelled = true;
break;
}
else
{
cleanup();
m_frame->PopTool( aTool );
cancelled = true;
break;
}
}
else if( evt->IsAction( &PCB_ACTIONS::layerChanged ) )
{
if( m_layer != m_frame->GetActiveLayer() )
{
m_layer = m_frame->GetActiveLayer();
m_stroke.SetWidth( getSegmentWidth( m_layer ) );
m_stroke.SetPlotStyle( PLOT_DASH_TYPE::DEFAULT );
m_stroke.SetColor( COLOR4D::UNSPECIFIED );
}
if( graphic )
{
if( !m_view->IsLayerVisible( m_layer ) )
{
m_frame->GetAppearancePanel()->SetLayerVisible( m_layer, true );
m_frame->GetCanvas()->Refresh();
}
graphic->SetLayer( m_layer );
graphic->SetStroke( m_stroke );
m_view->Update( &preview );
frame()->SetMsgPanel( graphic );
}
else
{
evt->SetPassEvent();
}
}
else if( evt->IsAction( &PCB_ACTIONS::properties ) )
{
if( started )
{
frame()->OnEditItemRequest( graphic );
m_view->Update( &preview );
frame()->SetMsgPanel( graphic );
break;
}
else
{
evt->SetPassEvent();
}
}
else if( evt->IsClick( BUT_RIGHT ) )
{
m_menu.ShowContextMenu( selection() );
}
else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) )
{
if( !started )
{
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
if( aStartingPoint )
{
cursorPos = aStartingPoint.get();
aStartingPoint = NULLOPT;
}
// Init the new item attributes
graphic->SetShape( static_cast<SHAPE_T>( shape ) );
graphic->SetFilled( false );
graphic->SetStroke( m_stroke );
graphic->SetLayer( m_layer );
grid.SetSkipPoint( cursorPos );
twoPointManager.SetOrigin( cursorPos );
twoPointManager.SetEnd( cursorPos );
if( !isLocalOriginSet )
m_frame->GetScreen()->m_LocalOrigin = cursorPos;
preview.Add( graphic );
frame()->SetMsgPanel( graphic );
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( true );
if( !m_view->IsLayerVisible( m_layer ) )
{
m_frame->GetAppearancePanel()->SetLayerVisible( m_layer, true );
m_frame->GetCanvas()->Refresh();
}
updateSegmentFromGeometryMgr( twoPointManager, graphic );
started = true;
}
else if( shape == SHAPE_T::CIRCLE )
{
// No clever logic if drawing a circle
preview.Clear();
twoPointManager.Reset();
break;
}
else
{
PCB_SHAPE* snapItem = dyn_cast<PCB_SHAPE*>( grid.GetSnapped() );
if( twoPointManager.GetOrigin() == twoPointManager.GetEnd()
|| ( evt->IsDblClick( BUT_LEFT ) && shape == SHAPE_T::SEGMENT )
|| snapItem )
// User has clicked twice in the same spot
// or clicked on the end of an existing segment (closing a path)
{
BOARD_COMMIT commit( m_frame );
// If the user clicks on an existing snap point from a drawsegment
// we finish the segment as they are likely closing a path
if( snapItem && ( shape == SHAPE_T::RECT || graphic->GetLength() > 0.0 ) )
{
commit.Add( graphic );
commit.Push( _( "Draw a line segment" ) );
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, graphic );
}
else
{
delete graphic;
}
graphic = nullptr;
}
preview.Clear();
twoPointManager.Reset();
break;
}
twoPointManager.SetEnd( cursorPos );
}
else if( evt->IsMotion() )
{
// 45 degree lines
if( started && Is45Limited() )
{
const VECTOR2I lineVector( cursorPos - VECTOR2I( twoPointManager.GetOrigin() ) );
// get a restricted 45/H/V line from the last fixed point to the cursor
auto newEnd = GetVectorSnapped45( lineVector, ( shape == SHAPE_T::RECT ) );
m_controls->ForceCursorPosition( true, VECTOR2I( twoPointManager.GetEnd() ) );
twoPointManager.SetEnd( twoPointManager.GetOrigin() + newEnd );
twoPointManager.SetAngleSnap( true );
}
else
{
twoPointManager.SetEnd( cursorPos );
twoPointManager.SetAngleSnap( false );
}
updateSegmentFromGeometryMgr( twoPointManager, graphic );
m_view->Update( &preview );
m_view->Update( &twoPointAsst );
}
else if( evt->IsAction( &PCB_ACTIONS::incWidth ) )
{
m_stroke.SetWidth( m_stroke.GetWidth() + WIDTH_STEP );
graphic->SetStroke( m_stroke );
m_view->Update( &preview );
frame()->SetMsgPanel( graphic );
}
else if( evt->IsAction( &PCB_ACTIONS::decWidth ) )
{
if( (unsigned) m_stroke.GetWidth() > WIDTH_STEP )
{
m_stroke.SetWidth( m_stroke.GetWidth() - WIDTH_STEP );
graphic->SetStroke( m_stroke );
m_view->Update( &preview );
frame()->SetMsgPanel( graphic );
}
}
else if( evt->IsAction( &ACTIONS::resetLocalCoords ) )
{
isLocalOriginSet = true;
evt->SetPassEvent();
}
else if( evt->IsAction( &ACTIONS::updateUnits ) )
{
if( frame()->GetUserUnits() != userUnits )
{
userUnits = frame()->GetUserUnits();
twoPointAsst.SetUnits( userUnits );
m_view->Update( &twoPointAsst );
}
evt->SetPassEvent();
}
else
{
evt->SetPassEvent();
}
}
if( !isLocalOriginSet ) // reset the relative coordinate if it was not set before
m_frame->GetScreen()->m_LocalOrigin = VECTOR2D( 0, 0 );
m_view->Remove( &twoPointAsst );
m_view->Remove( &preview );
if( selection().Empty() )
m_frame->SetMsgPanel( board() );
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
m_controls->ForceCursorPosition( false );
return !cancelled;
}
/**
* Update an arc PCB_SHAPE from the current state of an Arc Geometry Manager.
*/
static void updateArcFromConstructionMgr( const KIGFX::PREVIEW::ARC_GEOM_MANAGER& aMgr,
PCB_SHAPE& aArc )
{
VECTOR2I vec = aMgr.GetOrigin();
aArc.SetCenter( vec );
vec = aMgr.GetStartRadiusEnd();
aArc.SetStart( vec );
vec = aMgr.GetEndRadiusEnd();
aArc.SetEnd( vec );
}
bool DRAWING_TOOL::drawArc( const std::string& aTool, PCB_SHAPE** aGraphic,
OPT<VECTOR2D> aStartingPoint )
{
PCB_SHAPE*& graphic = *aGraphic;
if( m_layer != m_frame->GetActiveLayer() )
{
m_layer = m_frame->GetActiveLayer();
m_stroke.SetWidth( getSegmentWidth( m_layer ) );
m_stroke.SetPlotStyle( PLOT_DASH_TYPE::DEFAULT );
m_stroke.SetColor( COLOR4D::UNSPECIFIED );
}
// Arc geometric construction manager
KIGFX::PREVIEW::ARC_GEOM_MANAGER arcManager;
// Arc drawing assistant overlay
KIGFX::PREVIEW::ARC_ASSISTANT arcAsst( arcManager, m_frame->GetUserUnits() );
// Add a VIEW_GROUP that serves as a preview for the new item
PCB_SELECTION preview;
m_view->Add( &preview );
m_view->Add( &arcAsst );
PCB_GRID_HELPER grid( m_toolMgr, m_frame->GetMagneticItemsSettings() );
auto setCursor =
[&]()
{
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
};
auto cleanup =
[&] ()
{
preview.Clear();
delete *aGraphic;
*aGraphic = nullptr;
};
m_controls->ShowCursor( true );
m_controls->ForceCursorPosition( false );
// Set initial cursor
setCursor();
bool firstPoint = false;
bool cancelled = false;
m_toolMgr->RunAction( ACTIONS::refreshPreview );
if( aStartingPoint )
m_toolMgr->PrimeTool(aStartingPoint.get() );
// Main loop: keep receiving events
while( TOOL_EVENT* evt = Wait() )
{
if( firstPoint )
m_frame->SetMsgPanel( graphic );
setCursor();
graphic->SetLayer( m_layer );
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
VECTOR2I cursorPos = grid.BestSnapAnchor( m_controls->GetMousePosition(), graphic );
m_controls->ForceCursorPosition( true, cursorPos );
if( evt->IsCancelInteractive() )
{
cleanup();
if( !firstPoint )
{
// We've handled the cancel event. Don't cancel other tools
evt->SetPassEvent( false );
m_frame->PopTool( aTool );
cancelled = true;
}
break;
}
else if( evt->IsActivate() )
{
if( evt->IsPointEditor() )
{
// don't exit (the point editor runs in the background)
}
else if( evt->IsMoveTool() )
{
cleanup();
// leave ourselves on the stack so we come back after the move
cancelled = true;
break;
}
else
{
cleanup();
m_frame->PopTool( aTool );
cancelled = true;
break;
}
}
else if( evt->IsClick( BUT_LEFT ) )
{
if( !firstPoint )
{
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( true );
// Init the new item attributes
// (non-geometric, those are handled by the manager)
graphic->SetShape( SHAPE_T::ARC );
graphic->SetStroke( m_stroke );
if( !m_view->IsLayerVisible( m_layer ) )
{
m_frame->GetAppearancePanel()->SetLayerVisible( m_layer, true );
m_frame->GetCanvas()->Refresh();
}
preview.Add( graphic );
frame()->SetMsgPanel( graphic );
firstPoint = true;
}
arcManager.AddPoint( cursorPos, true );
}
else if( evt->IsAction( &PCB_ACTIONS::deleteLastPoint ) )
{
arcManager.RemoveLastPoint();
}
else if( evt->IsMotion() )
{
// set angle snap
arcManager.SetAngleSnap( Is45Limited() );
// update, but don't step the manager state
arcManager.AddPoint( cursorPos, false );
}
else if( evt->IsAction( &PCB_ACTIONS::layerChanged ) )
{
if( m_layer != m_frame->GetActiveLayer() )
{
m_layer = m_frame->GetActiveLayer();
m_stroke.SetWidth( getSegmentWidth( m_layer ) );
m_stroke.SetPlotStyle( PLOT_DASH_TYPE::DEFAULT );
m_stroke.SetColor( COLOR4D::UNSPECIFIED );
}
if( graphic )
{
if( !m_view->IsLayerVisible( m_layer ) )
{
m_frame->GetAppearancePanel()->SetLayerVisible( m_layer, true );
m_frame->GetCanvas()->Refresh();
}
graphic->SetLayer( m_layer );
graphic->SetStroke( m_stroke );
m_view->Update( &preview );
frame()->SetMsgPanel( graphic );
}
else
{
evt->SetPassEvent();
}
}
else if( evt->IsAction( &PCB_ACTIONS::properties ) )
{
if( arcManager.GetStep() == KIGFX::PREVIEW::ARC_GEOM_MANAGER::SET_START )
{
graphic->SetArcAngleAndEnd( ANGLE_90 );
frame()->OnEditItemRequest( graphic );
m_view->Update( &preview );
frame()->SetMsgPanel( graphic );
break;
}
// Don't show the edit panel if we can't represent the arc with it
else if( ( arcManager.GetStep() == KIGFX::PREVIEW::ARC_GEOM_MANAGER::SET_ANGLE )
&& ( arcManager.GetStartRadiusEnd() != arcManager.GetEndRadiusEnd() ) )
{
frame()->OnEditItemRequest( graphic );
m_view->Update( &preview );
frame()->SetMsgPanel( graphic );
break;
}
else
{
evt->SetPassEvent();
}
}
else if( evt->IsClick( BUT_RIGHT ) )
{
m_menu.ShowContextMenu( selection() );
}
else if( evt->IsAction( &PCB_ACTIONS::incWidth ) )
{
m_stroke.SetWidth( m_stroke.GetWidth() + WIDTH_STEP );
graphic->SetStroke( m_stroke );
m_view->Update( &preview );
frame()->SetMsgPanel( graphic );
}
else if( evt->IsAction( &PCB_ACTIONS::decWidth ) )
{
if( (unsigned) m_stroke.GetWidth() > WIDTH_STEP )
{
m_stroke.SetWidth( m_stroke.GetWidth() - WIDTH_STEP );
graphic->SetStroke( m_stroke );
m_view->Update( &preview );
frame()->SetMsgPanel( graphic );
}
}
else if( evt->IsAction( &PCB_ACTIONS::arcPosture ) )
{
arcManager.ToggleClockwise();
}
else if( evt->IsAction( &ACTIONS::updateUnits ) )
{
arcAsst.SetUnits( frame()->GetUserUnits() );
m_view->Update( &arcAsst );
evt->SetPassEvent();
}
else
{
evt->SetPassEvent();
}
if( arcManager.IsComplete() )
{
break;
}
else if( arcManager.HasGeometryChanged() )
{
updateArcFromConstructionMgr( arcManager, *graphic );
m_view->Update( &preview );
m_view->Update( &arcAsst );
if( firstPoint )
frame()->SetMsgPanel( graphic );
else
frame()->SetMsgPanel( board() );
}
}
preview.Remove( graphic );
m_view->Remove( &arcAsst );
m_view->Remove( &preview );
if( selection().Empty() )
m_frame->SetMsgPanel( board() );
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
m_controls->ForceCursorPosition( false );
return !cancelled;
}
bool DRAWING_TOOL::getSourceZoneForAction( ZONE_MODE aMode, ZONE** aZone )
{
bool clearSelection = false;
*aZone = nullptr;
// not an action that needs a source zone
if( aMode == ZONE_MODE::ADD || aMode == ZONE_MODE::GRAPHIC_POLYGON )
return true;
PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
const PCB_SELECTION& selection = selTool->GetSelection();
if( selection.Empty() )
{
clearSelection = true;
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
}
// we want a single zone
if( selection.Size() == 1 )
*aZone = dyn_cast<ZONE*>( selection[0] );
// expected a zone, but didn't get one
if( !*aZone )
{
if( clearSelection )
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
return false;
}
return true;
}
int DRAWING_TOOL::DrawZone( const TOOL_EVENT& aEvent )
{
if( m_isFootprintEditor && !m_frame->GetModel() )
return 0;
ZONE_MODE zoneMode = aEvent.Parameter<ZONE_MODE>();
MODE drawMode = MODE::ZONE;
if( aEvent.IsAction( &PCB_ACTIONS::drawRuleArea ) )
drawMode = MODE::KEEPOUT;
if( aEvent.IsAction( &PCB_ACTIONS::drawPolygon ) )
drawMode = MODE::GRAPHIC_POLYGON;
SCOPED_DRAW_MODE scopedDrawMode( m_mode, drawMode );
// get a source zone, if we need one. We need it for:
// ZONE_MODE::CUTOUT (adding a hole to the source zone)
// ZONE_MODE::SIMILAR (creating a new zone using settings of source zone
ZONE* sourceZone = nullptr;
if( !getSourceZoneForAction( zoneMode, &sourceZone ) )
return 0;
// Turn zones on if they are off, so that the created object will be visible after completion
m_frame->SetObjectVisible( LAYER_ZONES );
ZONE_CREATE_HELPER::PARAMS params;
params.m_keepout = drawMode == MODE::KEEPOUT;
params.m_mode = zoneMode;
params.m_sourceZone = sourceZone;
if( zoneMode == ZONE_MODE::SIMILAR )
params.m_layer = sourceZone->GetLayer();
else
params.m_layer = m_frame->GetActiveLayer();
ZONE_CREATE_HELPER zoneTool( *this, params );
// the geometry manager which handles the zone geometry, and hands the calculated points
// over to the zone creator tool
POLYGON_GEOM_MANAGER polyGeomMgr( zoneTool );
bool started = false;
PCB_GRID_HELPER grid( m_toolMgr, m_frame->GetMagneticItemsSettings() );
STATUS_TEXT_POPUP status( m_frame );
status.SetTextColor( wxColour( 255, 0, 0 ) );
status.SetText( _( "Self-intersecting polygons are not allowed" ) );
std::string tool = aEvent.GetCommandStr().get();
m_frame->PushTool( tool );
auto setCursor =
[&]()
{
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
};
auto cleanup =
[&] ()
{
polyGeomMgr.Reset();
started = false;
grid.ClearSkipPoint();
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
};
Activate();
// Must be done after Activate() so that it gets set into the correct context
m_controls->ShowCursor( true );
m_controls->ForceCursorPosition( false );
// Set initial cursor
setCursor();
if( aEvent.HasPosition() )
m_toolMgr->PrimeTool( aEvent.Position() );
// Main loop: keep receiving events
while( TOOL_EVENT* evt = Wait() )
{
setCursor();
LSET layers( m_frame->GetActiveLayer() );
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
VECTOR2I cursorPos = evt->HasPosition() ? evt->Position() : m_controls->GetMousePosition();
cursorPos = grid.BestSnapAnchor( cursorPos, layers );
m_controls->ForceCursorPosition( true, cursorPos );
polyGeomMgr.SetLeaderMode( Is45Limited() ? POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45
: POLYGON_GEOM_MANAGER::LEADER_MODE::DIRECT );
if( evt->IsCancelInteractive() )
{
if( polyGeomMgr.IsPolygonInProgress() )
{
cleanup();
}
else
{
// We've handled the cancel event. Don't cancel other tools
evt->SetPassEvent( false );
m_frame->PopTool( tool );
break;
}
}
else if( evt->IsActivate() )
{
if( polyGeomMgr.IsPolygonInProgress() )
cleanup();
if( evt->IsPointEditor() )
{
// don't exit (the point editor runs in the background)
}
else if( evt->IsMoveTool() )
{
// leave ourselves on the stack so we come back after the move
break;
}
else
{
m_frame->PopTool( tool );
break;
}
}
else if( evt->IsAction( &PCB_ACTIONS::layerChanged ) )
{
if( zoneMode != ZONE_MODE::SIMILAR )
params.m_layer = frame()->GetActiveLayer();
if( !m_view->IsLayerVisible( params.m_layer ) )
{
m_frame->GetAppearancePanel()->SetLayerVisible( params.m_layer, true );
m_frame->GetCanvas()->Refresh();
}
}
else if( evt->IsClick( BUT_RIGHT ) )
{
m_menu.ShowContextMenu( selection() );
}
// events that lock in nodes
else if( evt->IsClick( BUT_LEFT )
|| evt->IsDblClick( BUT_LEFT )
|| evt->IsAction( &PCB_ACTIONS::closeOutline ) )
{
// Check if it is double click / closing line (so we have to finish the zone)
const bool endPolygon = evt->IsDblClick( BUT_LEFT )
|| evt->IsAction( &PCB_ACTIONS::closeOutline )
|| polyGeomMgr.NewPointClosesOutline( cursorPos );
if( endPolygon )
{
polyGeomMgr.SetFinished();
polyGeomMgr.Reset();
started = false;
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
}
// adding a corner
else if( polyGeomMgr.AddPoint( cursorPos ) )
{
if( !started )
{
started = true;
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( true );
if( !m_view->IsLayerVisible( params.m_layer ) )
{
m_frame->GetAppearancePanel()->SetLayerVisible( params.m_layer, true );
m_frame->GetCanvas()->Refresh();
}
}
}
}
else if( evt->IsAction( &PCB_ACTIONS::deleteLastPoint ) )
{
polyGeomMgr.DeleteLastCorner();
if( !polyGeomMgr.IsPolygonInProgress() )
{
// report finished as an empty shape
polyGeomMgr.SetFinished();
// start again
started = false;
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
}
}
else if( polyGeomMgr.IsPolygonInProgress()
&& ( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) )
{
polyGeomMgr.SetCursorPosition( cursorPos );
if( polyGeomMgr.IsSelfIntersecting( true ) )
{
VECTOR2I p = wxGetMousePosition() + wxPoint( 20, 20 );
status.Move( p );
status.PopupFor( 1500 );
}
else
{
status.Hide();
}
}
else if( evt->IsAction( &PCB_ACTIONS::properties ) )
{
if( started )
{
frame()->OnEditItemRequest( zoneTool.GetZone() );
zoneTool.OnGeometryChange( polyGeomMgr );
frame()->SetMsgPanel( zoneTool.GetZone() );
}
else
{
evt->SetPassEvent();
}
}
/*else if( evt->IsAction( &ACTIONS::updateUnits ) )
{
// If we ever have an assistant here that reports dimensions, we'll want to
// update its units here....
// zoneAsst.SetUnits( frame()->GetUserUnits() );
// m_view->Update( &zoneAsst );
evt->SetPassEvent();
}*/
else
{
evt->SetPassEvent();
}
} // end while
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
m_controls->ForceCursorPosition( false );
controls()->SetAutoPan( false );
m_controls->CaptureCursor( false );
return 0;
}
int DRAWING_TOOL::DrawVia( const TOOL_EVENT& aEvent )
{
if( m_isFootprintEditor )
return 0;
struct VIA_PLACER : public INTERACTIVE_PLACER_BASE
{
PCB_BASE_EDIT_FRAME* m_frame;
PCB_GRID_HELPER m_gridHelper;
std::shared_ptr<DRC_ENGINE> m_drcEngine;
int m_drcEpsilon;
int m_worstClearance;
bool m_allowDRCViolations;
VIA_PLACER( PCB_BASE_EDIT_FRAME* aFrame ) :
m_frame( aFrame ),
m_gridHelper( aFrame->GetToolManager(), aFrame->GetMagneticItemsSettings() ),
m_drcEngine( aFrame->GetBoard()->GetDesignSettings().m_DRCEngine ),
m_drcEpsilon( aFrame->GetBoard()->GetDesignSettings().GetDRCEpsilon() ),
m_worstClearance( 0 )
{
ROUTER_TOOL* router = m_frame->GetToolManager()->GetTool<ROUTER_TOOL>();
m_allowDRCViolations = router->Router()->Settings().AllowDRCViolations();
try
{
m_drcEngine->InitEngine( aFrame->GetDesignRulesPath() );
DRC_CONSTRAINT constraint;
if( m_drcEngine->QueryWorstConstraint( CLEARANCE_CONSTRAINT, constraint ) )
m_worstClearance = constraint.GetValue().Min();
if( m_drcEngine->QueryWorstConstraint( HOLE_CLEARANCE_CONSTRAINT, constraint ) )
m_worstClearance = std::max( m_worstClearance, constraint.GetValue().Min() );
for( FOOTPRINT* footprint : aFrame->GetBoard()->Footprints() )
{
for( PAD* pad : footprint->Pads() )
m_worstClearance = std::max( m_worstClearance, pad->GetLocalClearance() );
}
}
catch( PARSE_ERROR& )
{
}
}
virtual ~VIA_PLACER()
{
}
PCB_TRACK* findTrack( PCB_VIA* aVia )
{
const LSET lset = aVia->GetLayerSet();
VECTOR2I position = aVia->GetPosition();
BOX2I bbox = aVia->GetBoundingBox();
std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> items;
KIGFX::PCB_VIEW* view = m_frame->GetCanvas()->GetView();
std::vector<PCB_TRACK*> possible_tracks;
view->Query( bbox, items );
for( const KIGFX::VIEW::LAYER_ITEM_PAIR& it : items )
{
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( it.first );
if( !(item->GetLayerSet() & lset ).any() )
continue;
if( PCB_TRACK* track = dyn_cast<PCB_TRACK*>( item ) )
{
if( TestSegmentHit( position, track->GetStart(), track->GetEnd(),
( track->GetWidth() + aVia->GetWidth() ) / 2 ) )
{
possible_tracks.push_back( track );
}
}
}
PCB_TRACK* return_track = nullptr;
int min_d = std::numeric_limits<int>::max();
for( PCB_TRACK* track : possible_tracks )
{
SEG test( track->GetStart(), track->GetEnd() );
int dist = ( test.NearestPoint( position ) - position ).EuclideanNorm();
if( dist < min_d )
{
min_d = dist;
return_track = track;
}
}
return return_track;
}
bool hasDRCViolation( PCB_VIA* aVia, BOARD_ITEM* aOther )
{
// It would really be better to know what particular nets a nettie should allow,
// but for now it is what it is.
if( DRC_ENGINE::IsNetTie( aOther ) )
return false;
DRC_CONSTRAINT constraint;
if( ( aOther->Type() == PCB_ZONE_T || aOther->Type() == PCB_FP_ZONE_T )
&& static_cast<ZONE*>( aOther )->GetIsRuleArea() )
{
ZONE* zone = static_cast<ZONE*>( aOther );
if( zone->GetDoNotAllowVias() )
return true;
constraint = m_drcEngine->EvalRules( DISALLOW_CONSTRAINT, aVia, nullptr,
UNDEFINED_LAYER );
if( constraint.m_DisallowFlags && constraint.GetSeverity() != RPT_SEVERITY_IGNORE )
return true;
return false;
}
BOARD_CONNECTED_ITEM* cItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( aOther );
if( cItem && cItem->GetNetCode() == aVia->GetNetCode() )
return false;
int clearance;
for( PCB_LAYER_ID layer : aOther->GetLayerSet().Seq() )
{
if( !IsCopperLayer( layer ) )
continue;
constraint = m_drcEngine->EvalRules( CLEARANCE_CONSTRAINT, aVia, aOther, layer );
clearance = constraint.GetValue().Min();
if( clearance >= 0 )
{
std::shared_ptr<SHAPE> viaShape = aVia->GetEffectiveShape( layer );
std::shared_ptr<SHAPE> otherShape = aOther->GetEffectiveShape( layer );
if( viaShape->Collide( otherShape.get(), clearance - m_drcEpsilon ) )
return true;
}
}
std::unique_ptr<SHAPE_SEGMENT> holeShape;
if( aOther->Type() == PCB_VIA_T )
{
PCB_VIA* via = static_cast<PCB_VIA*>( aOther );
VECTOR2I pos = via->GetPosition();
holeShape.reset( new SHAPE_SEGMENT( pos, pos, via->GetDrill() ) );
}
else if( aOther->Type() == PCB_PAD_T )
{
PAD* pad = static_cast<PAD*>( aOther );
if( pad->GetDrillSize().x )
holeShape.reset( new SHAPE_SEGMENT( *pad->GetEffectiveHoleShape() ) );
}
if( holeShape )
{
constraint = m_drcEngine->EvalRules( HOLE_CLEARANCE_CONSTRAINT, aVia, aOther,
UNDEFINED_LAYER );
clearance = constraint.GetValue().Min();
if( clearance >= 0 )
{
std::shared_ptr<SHAPE> viaShape = aVia->GetEffectiveShape( UNDEFINED_LAYER );
if( viaShape->Collide( holeShape.get(), clearance - m_drcEpsilon ) )
return true;
}
}
return false;
}
bool checkDRCViolation( PCB_VIA* aVia )
{
std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> items;
std::set<BOARD_ITEM*> checkedItems;
BOX2I bbox = aVia->GetBoundingBox();
bbox.Inflate( m_worstClearance );
m_frame->GetCanvas()->GetView()->Query( bbox, items );
for( std::pair<KIGFX::VIEW_ITEM*, int> it : items )
{
BOARD_ITEM* item = dynamic_cast<BOARD_ITEM*>( it.first );
if( !item )
continue;
if( ( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
&& !static_cast<ZONE*>( item )->GetIsRuleArea() )
{
continue; // stitching vias bind to zones, so ignore them
}
else if( item->Type() == PCB_FOOTPRINT_T || item->Type() == PCB_GROUP_T )
{
continue; // check against children, but not against footprint itself
}
else if( item->Type() == PCB_FP_TEXT_T
&& !static_cast<FP_TEXT*>( item )->IsVisible() )
{
continue; // ignore hidden items
}
else if( checkedItems.count( item ) )
{
continue; // already checked
}
if( hasDRCViolation( aVia, item ) )
return true;
checkedItems.insert( item );
}
return false;
}
PAD* findPad( PCB_VIA* aVia )
{
const VECTOR2I position = aVia->GetPosition();
const LSET lset = aVia->GetLayerSet();
for( FOOTPRINT* fp : m_board->Footprints() )
{
for( PAD* pad : fp->Pads() )
{
if( pad->HitTest( position ) && ( pad->GetLayerSet() & lset ).any() )
{
if( pad->GetNetCode() > 0 )
return pad;
}
}
}
return nullptr;
}
int findStitchedZoneNet( PCB_VIA* aVia )
{
const VECTOR2I position = aVia->GetPosition();
const LSET lset = aVia->GetLayerSet();
// first take the net of the active layer
if( lset.test( m_frame->GetActiveLayer() ) )
{
for( ZONE* z : m_board->Zones() )
{
if( z->IsOnLayer( m_frame->GetActiveLayer() ) )
{
if( z->HitTestFilledArea( m_frame->GetActiveLayer(), position ) )
return z->GetNetCode();
}
}
}
// none? take the topmost visible layer
for( PCB_LAYER_ID layer : LSET( m_board->GetVisibleLayers() & lset ).Seq() )
{
for( ZONE* z : m_board->Zones() )
{
if( z->IsOnLayer( m_frame->GetActiveLayer() ) )
{
if( z->HitTestFilledArea( layer, position ) )
return z->GetNetCode();
}
}
}
return -1;
}
void SnapItem( BOARD_ITEM *aItem ) override
{
m_gridHelper.SetSnap( !( m_modifiers & MD_SHIFT ) );
MAGNETIC_SETTINGS* settings = m_frame->GetMagneticItemsSettings();
PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
VECTOR2I position = via->GetPosition();
if( settings->tracks == MAGNETIC_OPTIONS::CAPTURE_ALWAYS && m_gridHelper.GetSnap() )
{
PCB_TRACK* track = findTrack( via );
if( track )
{
SEG trackSeg( track->GetStart(), track->GetEnd() );
VECTOR2I snap = m_gridHelper.AlignToSegment( position, trackSeg );
aItem->SetPosition( snap );
}
}
else if( settings->pads == MAGNETIC_OPTIONS::CAPTURE_ALWAYS && m_gridHelper.GetSnap() )
{
PAD* pad = findPad( via );
if( pad )
{
aItem->SetPosition( pad->GetPosition() );
}
}
}
bool PlaceItem( BOARD_ITEM* aItem, BOARD_COMMIT& aCommit ) override
{
WX_INFOBAR* infobar = m_frame->GetInfoBar();
PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
VECTOR2I viaPos = via->GetPosition();
PCB_TRACK* track = findTrack( via );
PAD* pad = findPad( via );
if( track )
via->SetNetCode( track->GetNetCode() );
else if( pad )
via->SetNetCode( pad->GetNetCode() );
else
via->SetNetCode( findStitchedZoneNet( via ) );
if( !m_allowDRCViolations && checkDRCViolation( via ) )
{
m_frame->ShowInfoBarError( _( "Via location violates DRC." ), true,
WX_INFOBAR::MESSAGE_TYPE::DRC_VIOLATION );
via->SetNetCode( 0 );
return false;
}
else
{
if( infobar->GetMessageType() == WX_INFOBAR::MESSAGE_TYPE::DRC_VIOLATION )
infobar->Dismiss();
}
if( !track && !pad )
{
via->SetNetCode( findStitchedZoneNet( via ) );
via->SetIsFree();
}
aCommit.Add( via );
if( track )
{
VECTOR2I trackStart = track->GetStart();
VECTOR2I trackEnd = track->GetEnd();
SEG trackSeg( trackStart, trackEnd );
VECTOR2I joint1;
VECTOR2I joint2;
auto insertChevron =
[&]()
{
if( ( trackStart - joint1 ).SquaredEuclideanNorm()
> ( trackStart - joint2 ).SquaredEuclideanNorm() )
{
std::swap( joint1, joint2 );
}
aCommit.Modify( track );
track->SetStart( trackStart );
track->SetEnd( joint1 );
PCB_TRACK* newTrack = dynamic_cast<PCB_TRACK*>( track->Clone() );
const_cast<KIID&>( newTrack->m_Uuid ) = KIID();
newTrack->SetStart( joint1 );
newTrack->SetEnd( viaPos );
aCommit.Add( newTrack );
newTrack = dynamic_cast<PCB_TRACK*>( track->Clone() );
const_cast<KIID&>( newTrack->m_Uuid ) = KIID();
newTrack->SetStart( viaPos );
newTrack->SetEnd( joint2 );
aCommit.Add( newTrack );
newTrack = dynamic_cast<PCB_TRACK*>( track->Clone() );
const_cast<KIID&>( newTrack->m_Uuid ) = KIID();
newTrack->SetStart( joint2 );
newTrack->SetEnd( trackEnd );
aCommit.Add( newTrack );
};
if( viaPos == trackStart || viaPos == trackEnd )
return true;
if( trackStart.x == trackEnd.x )
{
VECTOR2I splitPt = trackSeg.NearestPoint( viaPos );
if( splitPt.x != viaPos.x
&& abs( splitPt.x - viaPos.x ) < abs( splitPt.y - trackStart.y )
&& abs( splitPt.x - viaPos.x ) < abs( splitPt.y - trackEnd.y ) )
{
int offset = abs( splitPt.x - viaPos.x );
joint1 = VECTOR2I( splitPt.x, splitPt.y - offset );
joint2 = VECTOR2I( splitPt.x, splitPt.y + offset );
insertChevron();
return true;
}
}
else if( trackStart.y == trackEnd.y )
{
VECTOR2I splitPt = trackSeg.NearestPoint( viaPos );
if( splitPt.y != viaPos.y
&& abs( trackStart.y - viaPos.y ) < abs( trackStart.x - viaPos.x )
&& abs( trackEnd.y - viaPos.y ) < abs( trackEnd.x - viaPos.x ) )
{
int offset = abs( splitPt.y - viaPos.y );
joint1 = VECTOR2I( splitPt.x - offset, splitPt.y );
joint2 = VECTOR2I( splitPt.x + offset, splitPt.y );
insertChevron();
return true;
}
}
else if( abs( trackStart.y - trackEnd.y ) == abs( trackStart.x - trackEnd.x ) )
{
SEG horiz( VECTOR2I( -INT_MAX, viaPos.y ), VECTOR2I( INT_MAX, viaPos.y ) );
SEG vert( VECTOR2I( viaPos.x, -INT_MAX ), VECTOR2I( viaPos.x, INT_MAX ) );
if( track->GetBoundingBox().Contains( viaPos ) )
{
joint1 = trackSeg.Intersect( horiz, true, true ).get();
joint2 = trackSeg.Intersect( vert, true, true ).get();
insertChevron();
return true;
}
}
aCommit.Modify( track );
track->SetStart( trackStart );
track->SetEnd( viaPos );
PCB_TRACK* newTrack = dynamic_cast<PCB_TRACK*>( track->Clone() );
const_cast<KIID&>( newTrack->m_Uuid ) = KIID();
newTrack->SetStart( viaPos );
newTrack->SetEnd( trackEnd );
aCommit.Add( newTrack );
}
return true;
}
std::unique_ptr<BOARD_ITEM> CreateItem() override
{
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
PCB_VIA* via = new PCB_VIA( m_board );
via->SetNetCode( 0 );
via->SetViaType( bds.m_CurrentViaType );
// for microvias, the size and hole will be changed later.
via->SetWidth( bds.GetCurrentViaSize() );
via->SetDrill( bds.GetCurrentViaDrill() );
// Usual via is from copper to component.
// layer pair is B_Cu and F_Cu.
via->SetLayerPair( B_Cu, F_Cu );
PCB_LAYER_ID first_layer = m_frame->GetActiveLayer();
PCB_LAYER_ID last_layer;
// prepare switch to new active layer:
if( first_layer != m_frame->GetScreen()->m_Route_Layer_TOP )
last_layer = m_frame->GetScreen()->m_Route_Layer_TOP;
else
last_layer = m_frame->GetScreen()->m_Route_Layer_BOTTOM;
// Adjust the actual via layer pair
switch( via->GetViaType() )
{
case VIATYPE::BLIND_BURIED:
via->SetLayerPair( first_layer, last_layer );
break;
case VIATYPE::MICROVIA: // from external to the near neighbor inner layer
{
PCB_LAYER_ID last_inner_layer =
ToLAYER_ID( ( m_board->GetCopperLayerCount() - 2 ) );
if( first_layer == B_Cu )
last_layer = last_inner_layer;
else if( first_layer == F_Cu )
last_layer = In1_Cu;
else if( first_layer == last_inner_layer )
last_layer = B_Cu;
else if( first_layer == In1_Cu )
last_layer = F_Cu;
// else error: will be removed later
via->SetLayerPair( first_layer, last_layer );
// Update diameter and hole size, which where set previously for normal vias
NETCLASS* netClass = via->GetNetClass();
via->SetWidth( netClass->GetuViaDiameter() );
via->SetDrill( netClass->GetuViaDrill() );
}
break;
default:
break;
}
return std::unique_ptr<BOARD_ITEM>( via );
}
};
VIA_PLACER placer( frame() );
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::VIA );
doInteractiveItemPlacement( aEvent.GetCommandStr().get(), &placer, _( "Place via" ),
IPO_REPEAT | IPO_SINGLE_CLICK );
return 0;
}
int DRAWING_TOOL::getSegmentWidth( PCB_LAYER_ID aLayer ) const
{
assert( m_board );
return m_board->GetDesignSettings().GetLineThickness( aLayer );
}
const unsigned int DRAWING_TOOL::WIDTH_STEP = Millimeter2iu( 0.1 );
void DRAWING_TOOL::setTransitions()
{
Go( &DRAWING_TOOL::PlaceStackup, PCB_ACTIONS::placeStackup.MakeEvent() );
Go( &DRAWING_TOOL::PlaceCharacteristics, PCB_ACTIONS::placeCharacteristics.MakeEvent() );
Go( &DRAWING_TOOL::DrawLine, PCB_ACTIONS::drawLine.MakeEvent() );
Go( &DRAWING_TOOL::DrawZone, PCB_ACTIONS::drawPolygon.MakeEvent() );
Go( &DRAWING_TOOL::DrawRectangle, PCB_ACTIONS::drawRectangle.MakeEvent() );
Go( &DRAWING_TOOL::DrawCircle, PCB_ACTIONS::drawCircle.MakeEvent() );
Go( &DRAWING_TOOL::DrawArc, PCB_ACTIONS::drawArc.MakeEvent() );
Go( &DRAWING_TOOL::DrawDimension, PCB_ACTIONS::drawAlignedDimension.MakeEvent() );
Go( &DRAWING_TOOL::DrawDimension, PCB_ACTIONS::drawOrthogonalDimension.MakeEvent() );
Go( &DRAWING_TOOL::DrawDimension, PCB_ACTIONS::drawCenterDimension.MakeEvent() );
Go( &DRAWING_TOOL::DrawDimension, PCB_ACTIONS::drawRadialDimension.MakeEvent() );
Go( &DRAWING_TOOL::DrawDimension, PCB_ACTIONS::drawLeader.MakeEvent() );
Go( &DRAWING_TOOL::DrawZone, PCB_ACTIONS::drawZone.MakeEvent() );
Go( &DRAWING_TOOL::DrawZone, PCB_ACTIONS::drawRuleArea.MakeEvent() );
Go( &DRAWING_TOOL::DrawZone, PCB_ACTIONS::drawZoneCutout.MakeEvent() );
Go( &DRAWING_TOOL::DrawZone, PCB_ACTIONS::drawSimilarZone.MakeEvent() );
Go( &DRAWING_TOOL::DrawVia, PCB_ACTIONS::drawVia.MakeEvent() );
Go( &DRAWING_TOOL::PlaceText, PCB_ACTIONS::placeText.MakeEvent() );
Go( &DRAWING_TOOL::DrawRectangle, PCB_ACTIONS::drawTextBox.MakeEvent() );
Go( &DRAWING_TOOL::PlaceImportedGraphics, PCB_ACTIONS::placeImportedGraphics.MakeEvent() );
Go( &DRAWING_TOOL::SetAnchor, PCB_ACTIONS::setAnchor.MakeEvent() );
Go( &DRAWING_TOOL::ToggleHV45Mode, PCB_ACTIONS::toggleHV45Mode.MakeEvent() );
}