|
|
/*
* 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() );}
|