3600 lines
120 KiB
C++
3600 lines
120 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2014-2017 CERN
|
|
* Copyright (C) 2018-2024 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 "dialog_table_properties.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 <gal/graphics_abstraction_layer.h>
|
|
#include <geometry/geometry_utils.h>
|
|
#include <geometry/shape_segment.h>
|
|
#include <import_gfx/dialog_import_graphics.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 <status_popup.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 <tools/zone_filler_tool.h>
|
|
#include <view/view.h>
|
|
#include <widgets/appearance_controls.h>
|
|
#include <widgets/wx_infobar.h>
|
|
#include <wx/filedlg.h>
|
|
|
|
#include <bitmaps.h>
|
|
#include <board.h>
|
|
#include <board_commit.h>
|
|
#include <board_design_settings.h>
|
|
#include <confirm.h>
|
|
#include <footprint.h>
|
|
#include <macros.h>
|
|
#include <gal/painter.h>
|
|
#include <pcb_edit_frame.h>
|
|
#include <pcb_group.h>
|
|
#include <pcb_reference_image.h>
|
|
#include <pcb_text.h>
|
|
#include <pcb_textbox.h>
|
|
#include <pcb_table.h>
|
|
#include <pcb_tablecell.h>
|
|
#include <pcb_dimension.h>
|
|
#include <pcbnew_id.h>
|
|
#include <preview_items/arc_assistant.h>
|
|
#include <scoped_set_reset.h>
|
|
#include <string_utils.h>
|
|
#include <zone.h>
|
|
#include <fix_board_shape.h>
|
|
|
|
const unsigned int DRAWING_TOOL::COORDS_PADDING = pcbIUScale.mmToIU( 20 );
|
|
|
|
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();
|
|
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" ),
|
|
frame->MessageTextFromValue( via.m_Diameter ),
|
|
frame->MessageTextFromValue( via.m_Drill ) );
|
|
}
|
|
else
|
|
{
|
|
msg.Printf( _( "Via %s" ),
|
|
frame->MessageTextFromValue( 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, LINE_STYLE::DEFAULT, COLOR4D::UNSPECIFIED ),
|
|
m_pickerItem( nullptr ),
|
|
m_tuningPattern( nullptr )
|
|
{
|
|
}
|
|
|
|
|
|
DRAWING_TOOL::~DRAWING_TOOL()
|
|
{
|
|
}
|
|
|
|
|
|
bool DRAWING_TOOL::Init()
|
|
{
|
|
auto haveHighlight =
|
|
[&]( const SELECTION& sel )
|
|
{
|
|
KIGFX::RENDER_SETTINGS* cfg = m_toolMgr->GetView()->GetPainter()->GetSettings();
|
|
|
|
return !cfg->GetHighlightNetCodes().empty();
|
|
};
|
|
|
|
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 arcToolActive =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
return m_mode == MODE::ARC;
|
|
};
|
|
|
|
auto viaToolActive =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
return m_mode == MODE::VIA;
|
|
};
|
|
|
|
auto tuningToolActive =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
return m_mode == MODE::TUNING;
|
|
};
|
|
|
|
CONDITIONAL_MENU& 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 );
|
|
|
|
ctxMenu.AddItem( PCB_ACTIONS::clearHighlight, haveHighlight, 2 );
|
|
ctxMenu.AddSeparator( haveHighlight, 2 );
|
|
|
|
// tool-specific actions
|
|
ctxMenu.AddItem( PCB_ACTIONS::closeOutline, canCloseOutline, 200 );
|
|
ctxMenu.AddItem( PCB_ACTIONS::deleteLastPoint, canUndoPoint, 200 );
|
|
ctxMenu.AddItem( PCB_ACTIONS::arcPosture, arcToolActive, 200 );
|
|
ctxMenu.AddItem( PCB_ACTIONS::spacingIncrease, tuningToolActive, 200 );
|
|
ctxMenu.AddItem( PCB_ACTIONS::spacingDecrease, tuningToolActive, 200 );
|
|
ctxMenu.AddItem( PCB_ACTIONS::amplIncrease, tuningToolActive, 200 );
|
|
ctxMenu.AddItem( PCB_ACTIONS::amplDecrease, tuningToolActive, 200 );
|
|
ctxMenu.AddItem( PCB_ACTIONS::lengthTunerSettings, tuningToolActive, 200 );
|
|
|
|
ctxMenu.AddCheckItem( PCB_ACTIONS::toggleHV45Mode, !tuningToolActive, 250 );
|
|
ctxMenu.AddSeparator( 500 );
|
|
|
|
std::shared_ptr<VIA_SIZE_MENU> viaSizeMenu = std::make_shared<VIA_SIZE_MENU>();
|
|
viaSizeMenu->SetTool( this );
|
|
m_menu.RegisterSubMenu( 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>();
|
|
|
|
// Re-initialize session attributes
|
|
const BOARD_DESIGN_SETTINGS& bds = m_frame->GetDesignSettings();
|
|
|
|
m_layer = m_frame->GetActiveLayer();
|
|
m_stroke.SetWidth( bds.GetLineThickness( m_layer ) );
|
|
m_stroke.SetLineStyle( LINE_STYLE::DEFAULT );
|
|
m_stroke.SetColor( COLOR4D::UNSPECIFIED );
|
|
|
|
m_textAttrs.m_Size = bds.GetTextSize( m_layer );
|
|
m_textAttrs.m_StrokeWidth = bds.GetTextThickness( m_layer );
|
|
InferBold( &m_textAttrs );
|
|
m_textAttrs.m_Italic = bds.GetTextItalic( m_layer );
|
|
m_textAttrs.m_KeepUpright = bds.GetTextUpright( m_layer );
|
|
m_textAttrs.m_Mirrored = IsBackLayer( m_layer );
|
|
m_textAttrs.m_Halign = GR_TEXT_H_ALIGN_LEFT;
|
|
m_textAttrs.m_Valign = GR_TEXT_V_ALIGN_TOP;
|
|
|
|
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 );
|
|
|
|
BOARD_ITEM* parent = m_frame->GetModel();
|
|
PCB_SHAPE* line = new PCB_SHAPE( parent );
|
|
BOARD_COMMIT commit( m_frame );
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::LINE );
|
|
std::optional<VECTOR2D> startingPoint;
|
|
std::stack<PCB_SHAPE*> committedLines;
|
|
|
|
line->SetShape( SHAPE_T::SEGMENT );
|
|
line->SetFlags( IS_NEW );
|
|
|
|
if( aEvent.HasPosition() )
|
|
startingPoint = getViewControls()->GetCursorPosition( !aEvent.DisableGridSnapping() );
|
|
|
|
m_frame->PushTool( aEvent );
|
|
Activate();
|
|
|
|
while( drawShape( aEvent, &line, startingPoint, &committedLines ) )
|
|
{
|
|
if( line )
|
|
{
|
|
commit.Add( line );
|
|
commit.Push( _( "Draw Line" ) );
|
|
startingPoint = VECTOR2D( line->GetEnd() );
|
|
committedLines.push( line );
|
|
}
|
|
else
|
|
{
|
|
startingPoint = std::nullopt;
|
|
}
|
|
|
|
line = new PCB_SHAPE( parent );
|
|
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 );
|
|
BOARD_ITEM* parent = m_frame->GetModel();
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::RECTANGLE );
|
|
std::optional<VECTOR2D> startingPoint;
|
|
|
|
rect = isTextBox ? new PCB_TEXTBOX( parent ) : new PCB_SHAPE( parent );
|
|
rect->SetShape( SHAPE_T::RECTANGLE );
|
|
rect->SetFilled( false );
|
|
rect->SetFlags( IS_NEW );
|
|
|
|
if( aEvent.HasPosition() )
|
|
startingPoint = getViewControls()->GetCursorPosition( !aEvent.DisableGridSnapping() );
|
|
|
|
m_frame->PushTool( aEvent );
|
|
Activate();
|
|
|
|
while( drawShape( aEvent, &rect, startingPoint, nullptr ) )
|
|
{
|
|
if( rect )
|
|
{
|
|
bool cancelled = false;
|
|
|
|
if( PCB_TEXTBOX* textbox = dynamic_cast<PCB_TEXTBOX*>( rect ) )
|
|
cancelled = m_frame->ShowTextBoxPropertiesDialog( textbox ) != wxID_OK;
|
|
|
|
if( cancelled )
|
|
{
|
|
delete rect;
|
|
rect = nullptr;
|
|
}
|
|
else
|
|
{
|
|
rect->Normalize();
|
|
commit.Add( rect );
|
|
commit.Push( isTextBox ? _( "Draw Text Box" ) : _( "Draw Rectangle" ) );
|
|
|
|
m_toolMgr->RunAction<EDA_ITEM*>( PCB_ACTIONS::selectItem, rect );
|
|
}
|
|
}
|
|
|
|
rect = isTextBox ? new PCB_TEXTBOX( parent ) : new PCB_SHAPE( parent );
|
|
rect->SetShape( SHAPE_T::RECTANGLE );
|
|
rect->SetFilled( false );
|
|
rect->SetFlags( IS_NEW );
|
|
startingPoint = std::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 );
|
|
|
|
BOARD_ITEM* parent = m_frame->GetModel();
|
|
PCB_SHAPE* circle = new PCB_SHAPE( parent );
|
|
BOARD_COMMIT commit( m_frame );
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::CIRCLE );
|
|
std::optional<VECTOR2D> startingPoint;
|
|
|
|
circle->SetShape( SHAPE_T::CIRCLE );
|
|
circle->SetFilled( false );
|
|
circle->SetFlags( IS_NEW );
|
|
|
|
if( aEvent.HasPosition() )
|
|
startingPoint = getViewControls()->GetCursorPosition( !aEvent.DisableGridSnapping() );
|
|
|
|
m_frame->PushTool( aEvent );
|
|
Activate();
|
|
|
|
while( drawShape( aEvent, &circle, startingPoint, nullptr ) )
|
|
{
|
|
if( circle )
|
|
{
|
|
commit.Add( circle );
|
|
commit.Push( _( "Draw Circle" ) );
|
|
|
|
m_toolMgr->RunAction<EDA_ITEM*>( PCB_ACTIONS::selectItem, circle );
|
|
}
|
|
|
|
circle = new PCB_SHAPE( parent );
|
|
circle->SetShape( SHAPE_T::CIRCLE );
|
|
circle->SetFilled( false );
|
|
circle->SetFlags( IS_NEW );
|
|
|
|
startingPoint = std::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 );
|
|
|
|
BOARD_ITEM* parent = m_frame->GetModel();
|
|
PCB_SHAPE* arc = new PCB_SHAPE( parent );
|
|
BOARD_COMMIT commit( m_frame );
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::ARC );
|
|
std::optional<VECTOR2D> startingPoint;
|
|
|
|
arc->SetShape( SHAPE_T::ARC );
|
|
arc->SetFlags( IS_NEW );
|
|
|
|
m_frame->PushTool( aEvent );
|
|
Activate();
|
|
|
|
if( aEvent.HasPosition() )
|
|
startingPoint = aEvent.Position();
|
|
|
|
while( drawArc( aEvent, &arc, startingPoint ) )
|
|
{
|
|
if( arc )
|
|
{
|
|
commit.Add( arc );
|
|
commit.Push( _( "Draw Arc" ) );
|
|
|
|
m_toolMgr->RunAction<EDA_ITEM*>( PCB_ACTIONS::selectItem, arc );
|
|
}
|
|
|
|
arc = new PCB_SHAPE( parent );
|
|
arc->SetShape( SHAPE_T::ARC );
|
|
arc->SetFlags( IS_NEW );
|
|
|
|
startingPoint = std::nullopt;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::PlaceReferenceImage( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( m_inDrawingTool )
|
|
return 0;
|
|
|
|
REENTRANCY_GUARD guard( &m_inDrawingTool );
|
|
|
|
PCB_REFERENCE_IMAGE* image = aEvent.Parameter<PCB_REFERENCE_IMAGE*>();
|
|
bool immediateMode = image != nullptr;
|
|
PCB_GRID_HELPER grid( m_toolMgr, m_frame->GetMagneticItemsSettings() );
|
|
bool ignorePrimePosition = false;
|
|
COMMON_SETTINGS* common_settings = Pgm().GetCommonSettings();
|
|
|
|
VECTOR2I cursorPos = getViewControls()->GetCursorPosition();
|
|
PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
|
BOARD_COMMIT commit( m_frame );
|
|
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
// Add all the drawable symbols to preview
|
|
if( image )
|
|
{
|
|
image->SetPosition( cursorPos );
|
|
m_view->ClearPreview();
|
|
m_view->AddToPreview( image, false ); // Add, but not give ownership
|
|
}
|
|
|
|
m_frame->PushTool( aEvent );
|
|
|
|
auto setCursor =
|
|
[&]()
|
|
{
|
|
if( image )
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
|
|
else
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
|
|
};
|
|
|
|
auto cleanup =
|
|
[&] ()
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
m_view->ClearPreview();
|
|
m_view->RecacheAllItems();
|
|
delete image;
|
|
image = nullptr;
|
|
};
|
|
|
|
Activate();
|
|
|
|
// Must be done after Activate() so that it gets set into the correct context
|
|
getViewControls()->ShowCursor( true );
|
|
|
|
// Set initial cursor
|
|
setCursor();
|
|
|
|
// Prime the pump
|
|
if( image )
|
|
{
|
|
m_toolMgr->PostAction( ACTIONS::refreshPreview );
|
|
}
|
|
else 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();
|
|
|
|
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
|
|
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
|
|
cursorPos =
|
|
GetClampedCoords( grid.BestSnapAnchor( m_controls->GetMousePosition(),
|
|
m_frame->GetActiveLayer(), GRID_GRAPHICS ),
|
|
COORDS_PADDING );
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
|
|
if( evt->IsCancelInteractive() || ( image && evt->IsAction( &ACTIONS::undo ) ) )
|
|
{
|
|
if( image )
|
|
{
|
|
cleanup();
|
|
}
|
|
else
|
|
{
|
|
m_frame->PopTool( aEvent );
|
|
break;
|
|
}
|
|
|
|
if( immediateMode )
|
|
{
|
|
m_frame->PopTool( aEvent );
|
|
break;
|
|
}
|
|
}
|
|
else if( evt->IsActivate() )
|
|
{
|
|
if( image && evt->IsMoveTool() )
|
|
{
|
|
// We're already moving our own item; ignore the move tool
|
|
evt->SetPassEvent( false );
|
|
continue;
|
|
}
|
|
|
|
if( image )
|
|
{
|
|
m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel image creation." ) );
|
|
evt->SetPassEvent( false );
|
|
continue;
|
|
}
|
|
|
|
if( evt->IsMoveTool() )
|
|
{
|
|
// Leave ourselves on the stack so we come back after the move
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
m_frame->PopTool( aEvent );
|
|
break;
|
|
}
|
|
}
|
|
else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) )
|
|
{
|
|
if( !image )
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
wxFileDialog dlg( m_frame, _( "Choose Image" ), wxEmptyString, wxEmptyString,
|
|
_( "Image Files" ) + wxS( " " ) + wxImage::GetImageExtWildcard(),
|
|
wxFD_OPEN );
|
|
|
|
if( dlg.ShowModal() != wxID_OK )
|
|
continue;
|
|
|
|
// 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 = grid.Align( evt->Position() );
|
|
getViewControls()->WarpMouseCursor( cursorPos, true );
|
|
}
|
|
else
|
|
{
|
|
getViewControls()->PinCursorInsideNonAutoscrollArea( true );
|
|
cursorPos = getViewControls()->GetMousePosition();
|
|
}
|
|
|
|
cursorPos = getViewControls()->GetMousePosition( true );
|
|
|
|
wxString fullFilename = dlg.GetPath();
|
|
|
|
if( wxFileExists( fullFilename ) )
|
|
image = new PCB_REFERENCE_IMAGE( m_frame->GetModel(), cursorPos );
|
|
|
|
if( !image || !image->ReadImageFile( fullFilename ) )
|
|
{
|
|
wxMessageBox( _( "Could not load image from '%s'." ), fullFilename );
|
|
delete image;
|
|
image = nullptr;
|
|
continue;
|
|
}
|
|
|
|
image->SetFlags( IS_NEW | IS_MOVING );
|
|
image->SetLayer( m_frame->GetActiveLayer() );
|
|
|
|
m_view->ClearPreview();
|
|
m_view->AddToPreview( image, false ); // Add, but not give ownership
|
|
m_view->RecacheAllItems(); // Bitmaps are cached in Opengl
|
|
selectionTool->AddItemToSel( image, false );
|
|
|
|
getViewControls()->SetCursorPosition( cursorPos, false );
|
|
setCursor();
|
|
m_view->ShowPreview( true );
|
|
}
|
|
else
|
|
{
|
|
commit.Add( image );
|
|
commit.Push( _( "Place Image" ) );
|
|
|
|
m_toolMgr->RunAction<EDA_ITEM*>( PCB_ACTIONS::selectItem, image );
|
|
|
|
image = nullptr;
|
|
m_toolMgr->PostAction( ACTIONS::activatePointEditor );
|
|
|
|
m_view->ClearPreview();
|
|
|
|
if( immediateMode )
|
|
{
|
|
m_frame->PopTool( aEvent );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
// Warp after context menu only if dragging...
|
|
if( !image )
|
|
m_toolMgr->VetoContextMenuMouseWarp();
|
|
|
|
m_menu.ShowContextMenu( selectionTool->GetSelection() );
|
|
}
|
|
else if( image && ( evt->IsAction( &ACTIONS::refreshPreview )
|
|
|| evt->IsMotion() ) )
|
|
{
|
|
image->SetPosition( cursorPos );
|
|
m_view->ClearPreview();
|
|
m_view->AddToPreview( image, false ); // Add, but not give ownership
|
|
m_view->RecacheAllItems(); // Bitmaps are cached in Opengl
|
|
}
|
|
else if( image && evt->IsAction( &ACTIONS::doDelete ) )
|
|
{
|
|
cleanup();
|
|
}
|
|
else if( image && ( ZONE_FILLER_TOOL::IsZoneFillAction( evt )
|
|
|| evt->IsAction( &ACTIONS::redo ) ) )
|
|
{
|
|
wxBell();
|
|
}
|
|
else
|
|
{
|
|
evt->SetPassEvent();
|
|
}
|
|
|
|
// Enable autopanning and cursor capture only when there is an image to be placed
|
|
getViewControls()->SetAutoPan( image != nullptr );
|
|
getViewControls()->CaptureCursor( image != nullptr );
|
|
}
|
|
|
|
getViewControls()->SetAutoPan( false );
|
|
getViewControls()->CaptureCursor( false );
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
|
|
|
|
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();
|
|
PCB_TEXT* text = nullptr;
|
|
bool ignorePrimePosition = false;
|
|
const BOARD_DESIGN_SETTINGS& bds = m_frame->GetDesignSettings();
|
|
BOARD_COMMIT commit( m_frame );
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::TEXT );
|
|
PCB_GRID_HELPER grid( m_toolMgr, m_frame->GetMagneticItemsSettings() );
|
|
|
|
auto setCursor =
|
|
[&]()
|
|
{
|
|
if( text )
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
|
|
else
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::TEXT );
|
|
};
|
|
|
|
auto cleanup =
|
|
[&]()
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
m_controls->ForceCursorPosition( false );
|
|
m_controls->ShowCursor( true );
|
|
m_controls->SetAutoPan( false );
|
|
m_controls->CaptureCursor( false );
|
|
delete text;
|
|
text = nullptr;
|
|
};
|
|
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
m_frame->PushTool( aEvent );
|
|
|
|
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();
|
|
|
|
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
|
|
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
|
|
VECTOR2I cursorPos =
|
|
GetClampedCoords( grid.BestSnapAnchor( m_controls->GetMousePosition(),
|
|
m_frame->GetActiveLayer(), GRID_TEXT ),
|
|
COORDS_PADDING );
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
|
|
if( evt->IsCancelInteractive() || ( text && evt->IsAction( &ACTIONS::undo ) ) )
|
|
{
|
|
if( text )
|
|
{
|
|
cleanup();
|
|
}
|
|
else
|
|
{
|
|
m_frame->PopTool( aEvent );
|
|
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( aEvent );
|
|
break;
|
|
}
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
if( !text )
|
|
m_toolMgr->VetoContextMenuMouseWarp();
|
|
|
|
m_menu.ShowContextMenu( selection() );
|
|
}
|
|
else if( evt->IsClick( BUT_LEFT ) )
|
|
{
|
|
bool placing = text != nullptr;
|
|
|
|
if( !text )
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
m_controls->ForceCursorPosition( true, m_controls->GetCursorPosition() );
|
|
|
|
PCB_LAYER_ID layer = m_frame->GetActiveLayer();
|
|
TEXT_ATTRIBUTES textAttrs;
|
|
|
|
textAttrs.m_Size = bds.GetTextSize( layer );
|
|
textAttrs.m_StrokeWidth = bds.GetTextThickness( layer );
|
|
InferBold( &textAttrs );
|
|
textAttrs.m_Italic = bds.GetTextItalic( layer );
|
|
textAttrs.m_KeepUpright = bds.GetTextUpright( layer );
|
|
textAttrs.m_Mirrored = IsBackLayer( layer );
|
|
textAttrs.m_Halign = GR_TEXT_H_ALIGN_LEFT;
|
|
textAttrs.m_Valign = GR_TEXT_V_ALIGN_BOTTOM;
|
|
|
|
if( m_isFootprintEditor )
|
|
text = new PCB_TEXT( static_cast<FOOTPRINT*>( m_frame->GetModel() ) );
|
|
else
|
|
text = new PCB_TEXT( m_frame->GetModel() );
|
|
|
|
text->SetLayer( layer );
|
|
text->SetAttributes( textAttrs );
|
|
text->SetTextPos( cursorPos );
|
|
|
|
DIALOG_TEXT_PROPERTIES textDialog( m_frame, text );
|
|
bool cancelled;
|
|
|
|
RunMainStack( [&]()
|
|
{
|
|
// QuasiModal required for Scintilla auto-complete
|
|
cancelled = !textDialog.ShowQuasiModal();
|
|
} );
|
|
|
|
if( cancelled || NoPrintableChars( text->GetText() ) )
|
|
{
|
|
delete text;
|
|
text = nullptr;
|
|
}
|
|
else if( text->GetTextPos() != cursorPos )
|
|
{
|
|
// If the user modified the location then go ahead and place it there.
|
|
// Otherwise we'll drag.
|
|
placing = true;
|
|
}
|
|
|
|
if( text )
|
|
{
|
|
if( !m_view->IsLayerVisible( text->GetLayer() ) )
|
|
{
|
|
m_frame->GetAppearancePanel()->SetLayerVisible( text->GetLayer(), true );
|
|
m_frame->GetCanvas()->Refresh();
|
|
}
|
|
|
|
m_toolMgr->RunAction<EDA_ITEM*>( PCB_ACTIONS::selectItem, 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 );
|
|
|
|
commit.Add( text );
|
|
commit.Push( _( "Place Text" ) );
|
|
|
|
m_toolMgr->RunAction<EDA_ITEM*>( PCB_ACTIONS::selectItem, 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->PostAction( 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( text && ( ZONE_FILLER_TOOL::IsZoneFillAction( evt )
|
|
|| evt->IsAction( &ACTIONS::redo ) ) )
|
|
{
|
|
wxBell();
|
|
}
|
|
else if( text && evt->IsAction( &PCB_ACTIONS::properties ) )
|
|
{
|
|
frame()->OnEditItemRequest( text );
|
|
m_view->Update( &selection() );
|
|
frame()->SetMsgPanel( text );
|
|
}
|
|
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;
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::DrawTable( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( m_inDrawingTool )
|
|
return 0;
|
|
|
|
REENTRANCY_GUARD guard( &m_inDrawingTool );
|
|
|
|
PCB_TABLE* table = nullptr;
|
|
const BOARD_DESIGN_SETTINGS& bds = m_frame->GetDesignSettings();
|
|
BOARD_COMMIT commit( m_frame );
|
|
PCB_GRID_HELPER grid( m_toolMgr, m_frame->GetMagneticItemsSettings() );
|
|
|
|
// We might be running as the same shape in another co-routine. Make sure that one
|
|
// gets whacked.
|
|
m_toolMgr->DeactivateTool();
|
|
|
|
auto setCursor =
|
|
[&]()
|
|
{
|
|
if( table )
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
|
|
else
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
|
|
};
|
|
|
|
auto cleanup =
|
|
[&] ()
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
m_controls->ForceCursorPosition( false );
|
|
m_controls->ShowCursor( true );
|
|
m_controls->SetAutoPan( false );
|
|
m_controls->CaptureCursor( false );
|
|
delete table;
|
|
table = nullptr;
|
|
};
|
|
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
m_frame->PushTool( aEvent );
|
|
|
|
Activate();
|
|
// Must be done after Activate() so that it gets set into the correct context
|
|
getViewControls()->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();
|
|
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
|
|
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
|
|
VECTOR2I cursorPos =
|
|
GetClampedCoords( grid.BestSnapAnchor( m_controls->GetMousePosition(),
|
|
m_frame->GetActiveLayer(), GRID_TEXT ),
|
|
COORDS_PADDING );
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
|
|
if( evt->IsCancelInteractive() || ( table && evt->IsAction( &ACTIONS::undo ) ) )
|
|
{
|
|
if( table )
|
|
{
|
|
cleanup();
|
|
}
|
|
else
|
|
{
|
|
m_frame->PopTool( aEvent );
|
|
break;
|
|
}
|
|
}
|
|
else if( evt->IsActivate() )
|
|
{
|
|
if( table )
|
|
cleanup();
|
|
|
|
if( evt->IsMoveTool() )
|
|
{
|
|
// leave ourselves on the stack so we come back after the move
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
m_frame->PopTool( aEvent );
|
|
break;
|
|
}
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
// Warp after context menu only if dragging...
|
|
if( !table )
|
|
m_toolMgr->VetoContextMenuMouseWarp();
|
|
|
|
m_menu.ShowContextMenu( selection() );
|
|
}
|
|
else if( evt->IsClick( BUT_LEFT ) )
|
|
{
|
|
if( !table )
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
PCB_LAYER_ID layer = m_frame->GetActiveLayer();
|
|
|
|
table = new PCB_TABLE( m_frame->GetModel(), bds.GetLineThickness( layer ) );
|
|
table->SetLayer( layer );
|
|
table->SetColCount( 1 );
|
|
table->AddCell( new PCB_TABLECELL( table ) );
|
|
|
|
table->SetLayer( layer );
|
|
table->SetPosition( cursorPos );
|
|
|
|
if( !m_view->IsLayerVisible( layer ) )
|
|
{
|
|
m_frame->GetAppearancePanel()->SetLayerVisible( layer, true );
|
|
m_frame->GetCanvas()->Refresh();
|
|
}
|
|
|
|
m_toolMgr->RunAction<EDA_ITEM*>( PCB_ACTIONS::selectItem, table );
|
|
m_view->Update( &selection() );
|
|
|
|
// update the cursor so it looks correct before another event
|
|
setCursor();
|
|
}
|
|
else
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
table->ClearFlags();
|
|
table->Normalize();
|
|
|
|
DIALOG_TABLE_PROPERTIES dlg( m_frame, table );
|
|
|
|
// QuasiModal required for Scintilla auto-complete
|
|
if( dlg.ShowQuasiModal() == wxID_OK )
|
|
{
|
|
commit.Add( table, m_frame->GetScreen() );
|
|
commit.Push( _( "Draw Table" ) );
|
|
|
|
m_toolMgr->RunAction<EDA_ITEM*>( PCB_ACTIONS::selectItem, table );
|
|
m_toolMgr->PostAction( ACTIONS::activatePointEditor );
|
|
}
|
|
else
|
|
{
|
|
delete table;
|
|
}
|
|
|
|
table = nullptr;
|
|
}
|
|
}
|
|
else if( table && ( evt->IsAction( &ACTIONS::refreshPreview ) || evt->IsMotion() ) )
|
|
{
|
|
VECTOR2I fontSize = bds.GetTextSize( table->GetLayer() );
|
|
VECTOR2I gridSize = grid.GetGridSize( grid.GetItemGrid( table ) );
|
|
VECTOR2I origin( table->GetPosition() );
|
|
VECTOR2I requestedSize( cursorPos - origin );
|
|
|
|
int colCount = std::max( 1, requestedSize.x / ( fontSize.x * 15 ) );
|
|
int rowCount = std::max( 1, requestedSize.y / ( fontSize.y * 3 ) );
|
|
|
|
VECTOR2I cellSize( std::max( fontSize.x * 5, requestedSize.x / colCount ),
|
|
std::max( fontSize.y * 3, requestedSize.y / rowCount ) );
|
|
|
|
cellSize.x = KiROUND( (double) cellSize.x / gridSize.x ) * gridSize.x;
|
|
cellSize.y = KiROUND( (double) cellSize.y / gridSize.y ) * gridSize.y;
|
|
|
|
table->ClearCells();
|
|
table->SetColCount( colCount );
|
|
|
|
for( int col = 0; col < colCount; ++col )
|
|
table->SetColWidth( col, cellSize.x );
|
|
|
|
for( int row = 0; row < rowCount; ++row )
|
|
{
|
|
table->SetRowHeight( row, cellSize.y );
|
|
|
|
for( int col = 0; col < colCount; ++col )
|
|
{
|
|
PCB_TABLECELL* cell = new PCB_TABLECELL( table );
|
|
cell->SetPosition( origin + VECTOR2I( col * cellSize.x, row * cellSize.y ) );
|
|
cell->SetEnd( cell->GetPosition() + cellSize );
|
|
table->AddCell( cell );
|
|
}
|
|
}
|
|
|
|
selection().SetReferencePoint( cursorPos );
|
|
m_view->Update( &selection() );
|
|
m_frame->SetMsgPanel( table );
|
|
}
|
|
else if( table && evt->IsAction( &PCB_ACTIONS::properties ) )
|
|
{
|
|
frame()->OnEditItemRequest( table );
|
|
m_view->Update( &selection() );
|
|
frame()->SetMsgPanel( table );
|
|
}
|
|
else if( table && ( ZONE_FILLER_TOOL::IsZoneFillAction( evt )
|
|
|| evt->IsAction( &ACTIONS::redo ) ) )
|
|
{
|
|
wxBell();
|
|
}
|
|
else
|
|
{
|
|
evt->SetPassEvent();
|
|
}
|
|
|
|
// Enable autopanning and cursor capture only when there is a shape being drawn
|
|
getViewControls()->SetAutoPan( table != nullptr );
|
|
getViewControls()->CaptureCursor( table != nullptr );
|
|
}
|
|
|
|
getViewControls()->SetAutoPan( false );
|
|
getViewControls()->CaptureCursor( false );
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
|
|
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 );
|
|
|
|
m_frame->PushTool( aEvent );
|
|
|
|
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->PostAction( 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 && t != PCB_DIM_ORTHOGONAL_T )
|
|
{
|
|
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 = GetClampedCoords( grid.BestSnapAnchor( cursorPos, nullptr, GRID_GRAPHICS ),
|
|
COORDS_PADDING );
|
|
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
|
|
if( evt->IsCancelInteractive() || ( dimension && evt->IsAction( &ACTIONS::undo ) ) )
|
|
{
|
|
m_controls->SetAutoPan( false );
|
|
|
|
if( step != SET_ORIGIN ) // start from the beginning
|
|
{
|
|
cleanup();
|
|
}
|
|
else
|
|
{
|
|
m_frame->PopTool( aEvent );
|
|
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( aEvent );
|
|
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 ) )
|
|
{
|
|
if( !dimension )
|
|
m_toolMgr->VetoContextMenuMouseWarp();
|
|
|
|
m_menu.ShowContextMenu( selection() );
|
|
}
|
|
else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) )
|
|
{
|
|
switch( step )
|
|
{
|
|
case SET_ORIGIN:
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
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( originalEvent.IsAction( &PCB_ACTIONS::drawAlignedDimension ) )
|
|
{
|
|
dimension = new PCB_DIM_ALIGNED( m_frame->GetModel() );
|
|
setMeasurementAttributes( dimension );
|
|
}
|
|
else if( originalEvent.IsAction( &PCB_ACTIONS::drawOrthogonalDimension ) )
|
|
{
|
|
dimension = new PCB_DIM_ORTHOGONAL( m_frame->GetModel() );
|
|
setMeasurementAttributes( dimension );
|
|
}
|
|
else if( originalEvent.IsAction( &PCB_ACTIONS::drawCenterDimension ) )
|
|
{
|
|
dimension = new PCB_DIM_CENTER( m_frame->GetModel() );
|
|
}
|
|
else if( originalEvent.IsAction( &PCB_ACTIONS::drawRadialDimension ) )
|
|
{
|
|
dimension = new PCB_DIM_RADIAL( m_frame->GetModel() );
|
|
setMeasurementAttributes( dimension );
|
|
}
|
|
else if( originalEvent.IsAction( &PCB_ACTIONS::drawLeader ) )
|
|
{
|
|
dimension = new PCB_DIM_LEADER( m_frame->GetModel() );
|
|
dimension->SetTextPos( cursorPos );
|
|
}
|
|
else
|
|
{
|
|
wxFAIL_MSG( wxT( "Unhandled action in DRAWING_TOOL::DrawDimension" ) );
|
|
}
|
|
|
|
t = dimension->Type();
|
|
|
|
dimension->SetLayer( layer );
|
|
dimension->SetTextSize( boardSettings.GetTextSize( layer ) );
|
|
dimension->SetTextThickness( boardSettings.GetTextThickness( layer ) );
|
|
dimension->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 )
|
|
{
|
|
// 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 Dimension" ) );
|
|
|
|
// Run the edit immediately to set the leader text
|
|
if( t == PCB_DIM_LEADER_T )
|
|
frame()->OnEditItemRequest( dimension );
|
|
|
|
m_toolMgr->RunAction<EDA_ITEM*>( PCB_ACTIONS::selectItem, dimension );
|
|
|
|
break;
|
|
}
|
|
|
|
if( ++step >= FINISHED )
|
|
{
|
|
dimension = nullptr;
|
|
step = SET_ORIGIN;
|
|
m_controls->SetAutoPan( false );
|
|
m_controls->CaptureCursor( false );
|
|
}
|
|
else if( evt->IsDblClick( BUT_LEFT ) )
|
|
{
|
|
m_toolMgr->PostAction( PCB_ACTIONS::cursorClick );
|
|
}
|
|
}
|
|
else if( evt->IsMotion() )
|
|
{
|
|
switch( step )
|
|
{
|
|
case SET_END:
|
|
dimension->SetEnd( cursorPos );
|
|
|
|
if( Is45Limited() || t == PCB_DIM_CENTER_T )
|
|
constrainDimension( dimension );
|
|
|
|
if( t == PCB_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 )
|
|
{
|
|
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->SetTextPos( radialDim->GetKnee() + textOffset );
|
|
}
|
|
else if( t == PCB_DIM_LEADER_T )
|
|
{
|
|
VECTOR2I textOffset( dimension->GetArrowLength() * 10, 0 );
|
|
|
|
if( dimension->GetEnd().x < dimension->GetStart().x )
|
|
textOffset = -textOffset;
|
|
|
|
dimension->SetTextPos( dimension->GetEnd() + textOffset );
|
|
}
|
|
|
|
dimension->Update();
|
|
break;
|
|
|
|
case SET_HEIGHT:
|
|
if( t == PCB_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 )
|
|
{
|
|
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( dimension && evt->IsAction( &PCB_ACTIONS::layerChanged ) )
|
|
{
|
|
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->SetTextSize( boardSettings.GetTextSize( layer ) );
|
|
dimension->SetTextThickness( boardSettings.GetTextThickness( layer ) );
|
|
dimension->SetItalic( boardSettings.GetTextItalic( layer ) );
|
|
dimension->SetLineThickness( boardSettings.GetLineThickness( layer ) );
|
|
dimension->Update();
|
|
|
|
m_view->Update( &preview );
|
|
frame()->SetMsgPanel( dimension );
|
|
}
|
|
else if( dimension && evt->IsAction( &PCB_ACTIONS::properties ) )
|
|
{
|
|
if( step == SET_END || step == SET_HEIGHT )
|
|
{
|
|
frame()->OnEditItemRequest( dimension );
|
|
dimension->Update();
|
|
frame()->SetMsgPanel( dimension );
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
wxBell();
|
|
}
|
|
}
|
|
else if( dimension && ( ZONE_FILLER_TOOL::IsZoneFillAction( evt )
|
|
|| evt->IsAction( &ACTIONS::redo ) ) )
|
|
{
|
|
wxBell();
|
|
}
|
|
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 );
|
|
|
|
DIALOG_IMPORT_GRAPHICS dlg( m_frame );
|
|
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 );
|
|
|
|
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;
|
|
PICKED_ITEMS_LIST groupUndoList;
|
|
PCB_LAYER_ID layer = F_Cu;
|
|
|
|
if( dlg.ShouldGroupItems() )
|
|
{
|
|
group = new PCB_GROUP( m_frame->GetModel() );
|
|
|
|
newItems.push_back( group );
|
|
selectedItems.push_back( group );
|
|
preview.Add( group );
|
|
}
|
|
|
|
if( dlg.ShouldFixDiscontinuities() )
|
|
{
|
|
std::vector<PCB_SHAPE*> shapeList;
|
|
std::vector<std::unique_ptr<PCB_SHAPE>> newShapes;
|
|
|
|
for( const std::unique_ptr<EDA_ITEM>& ptr : list )
|
|
{
|
|
if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( ptr.get() ) )
|
|
shapeList.push_back( shape );
|
|
}
|
|
|
|
ConnectBoardShapes( shapeList, newShapes, dlg.GetTolerance() );
|
|
|
|
for( std::unique_ptr<PCB_SHAPE>& ptr : newShapes )
|
|
{
|
|
ptr->SetParent( m_frame->GetBoard() );
|
|
list.push_back( std::move( ptr ) );
|
|
}
|
|
}
|
|
|
|
for( std::unique_ptr<EDA_ITEM>& ptr : list )
|
|
{
|
|
BOARD_ITEM* item = dynamic_cast<BOARD_ITEM*>( ptr.release() );
|
|
wxCHECK2( item, continue );
|
|
|
|
newItems.push_back( item );
|
|
|
|
if( group )
|
|
{
|
|
group->AddItem( item );
|
|
groupUndoList.PushItem( ITEM_PICKER( nullptr, item, UNDO_REDO::REGROUP ) );
|
|
}
|
|
else
|
|
{
|
|
selectedItems.push_back( item );
|
|
}
|
|
|
|
layer = item->GetLayer();
|
|
|
|
preview.Add( item );
|
|
}
|
|
|
|
// Clear the current selection then select the drawings so that edit tools work on them
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
EDA_ITEMS selItems( selectedItems.begin(), selectedItems.end() );
|
|
m_toolMgr->RunAction<EDA_ITEMS*>( PCB_ACTIONS::selectItems, &selItems );
|
|
|
|
if( !dlg.IsPlacementInteractive() )
|
|
{
|
|
for( BOARD_ITEM* item : newItems )
|
|
commit.Add( item );
|
|
|
|
if( groupUndoList.GetCount() > 0 )
|
|
commit.Stage( groupUndoList );
|
|
|
|
commit.Push( _( "Import Graphics" ) );
|
|
|
|
return 0;
|
|
}
|
|
|
|
if( !m_view->IsLayerVisible( layer ) )
|
|
{
|
|
m_frame->GetAppearancePanel()->SetLayerVisible( layer, true );
|
|
m_frame->GetCanvas()->Refresh();
|
|
}
|
|
|
|
m_view->Add( &preview );
|
|
|
|
m_frame->PushTool( aEvent );
|
|
|
|
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 );
|
|
PCB_GRID_HELPER grid( m_toolMgr, m_frame->GetMagneticItemsSettings() );
|
|
|
|
// Now move the new items to the current cursor position:
|
|
VECTOR2I cursorPos = m_controls->GetCursorPosition( !aEvent.DisableGridSnapping() );
|
|
VECTOR2I delta = cursorPos - static_cast<BOARD_ITEM*>( preview.GetTopLeftItem() )->GetPosition();
|
|
|
|
for( BOARD_ITEM* item : selectedItems )
|
|
item->Move( delta );
|
|
|
|
m_view->Update( &preview );
|
|
|
|
// Main loop: keep receiving events
|
|
while( TOOL_EVENT* evt = Wait() )
|
|
{
|
|
setCursor();
|
|
|
|
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
|
|
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
|
|
cursorPos = GetClampedCoords(
|
|
grid.BestSnapAnchor( m_controls->GetMousePosition(), layer, GRID_GRAPHICS ),
|
|
COORDS_PADDING );
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
|
|
if( evt->IsCancelInteractive() || evt->IsActivate() )
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
if( group )
|
|
{
|
|
preview.Remove( group );
|
|
group->RemoveAll();
|
|
}
|
|
|
|
for( BOARD_ITEM* item : newItems )
|
|
delete item;
|
|
|
|
break;
|
|
}
|
|
else if( evt->IsMotion() )
|
|
{
|
|
delta = cursorPos - static_cast<BOARD_ITEM*>( preview.GetTopLeftItem() )->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 );
|
|
|
|
if( groupUndoList.GetCount() > 0 )
|
|
commit.Stage( groupUndoList );
|
|
|
|
commit.Push( _( "Import Graphics" ) );
|
|
|
|
break; // This is a one-shot command, not a tool
|
|
}
|
|
else if( ZONE_FILLER_TOOL::IsZoneFillAction( evt ) )
|
|
{
|
|
wxBell();
|
|
}
|
|
else
|
|
{
|
|
evt->SetPassEvent();
|
|
}
|
|
}
|
|
|
|
preview.Clear();
|
|
m_view->Remove( &preview );
|
|
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
|
|
m_controls->ForceCursorPosition( false );
|
|
|
|
m_frame->PopTool( aEvent );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::SetAnchor( const TOOL_EVENT& aEvent )
|
|
{
|
|
// Make sense only in FP editor
|
|
if( !m_isFootprintEditor )
|
|
return 0;
|
|
|
|
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 );
|
|
|
|
m_frame->PushTool( aEvent );
|
|
|
|
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 Footprint Anchor" ) );
|
|
|
|
// Usually, we do not need to change twice the anchor position,
|
|
// so deselect the active tool
|
|
m_frame->PopTool( aEvent );
|
|
break;
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
m_menu.ShowContextMenu( selection() );
|
|
}
|
|
else if( evt->IsCancelInteractive() || evt->IsActivate() )
|
|
{
|
|
m_frame->PopTool( aEvent );
|
|
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::drawShape( const TOOL_EVENT& aTool, PCB_SHAPE** aGraphic,
|
|
std::optional<VECTOR2D> aStartingPoint,
|
|
std::stack<PCB_SHAPE*>* aCommittedGraphics )
|
|
{
|
|
SHAPE_T shape = ( *aGraphic )->GetShape();
|
|
|
|
// Only three shapes are currently supported
|
|
wxASSERT( shape == SHAPE_T::SEGMENT || shape == SHAPE_T::CIRCLE || shape == SHAPE_T::RECTANGLE );
|
|
|
|
const BOARD_DESIGN_SETTINGS& bds = m_frame->GetDesignSettings();
|
|
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( bds.GetLineThickness( m_layer ) );
|
|
m_stroke.SetLineStyle( LINE_STYLE::DEFAULT );
|
|
m_stroke.SetColor( COLOR4D::UNSPECIFIED );
|
|
|
|
m_textAttrs.m_Size = bds.GetTextSize( m_layer );
|
|
m_textAttrs.m_StrokeWidth = bds.GetTextThickness( m_layer );
|
|
InferBold( &m_textAttrs );
|
|
m_textAttrs.m_Italic = bds.GetTextItalic( m_layer );
|
|
m_textAttrs.m_KeepUpright = bds.GetTextUpright( m_layer );
|
|
m_textAttrs.m_Mirrored = IsBackLayer( m_layer );
|
|
m_textAttrs.m_Halign = GR_TEXT_H_ALIGN_LEFT;
|
|
m_textAttrs.m_Valign = GR_TEXT_V_ALIGN_TOP;
|
|
}
|
|
|
|
// geometric construction manager
|
|
KIGFX::PREVIEW::TWO_POINT_GEOMETRY_MANAGER twoPointMgr;
|
|
|
|
// 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( twoPointMgr, pcbIUScale, userUnits, geomShape );
|
|
|
|
// Add a VIEW_GROUP that serves as a preview for the new item
|
|
m_preview.Clear();
|
|
m_view->Add( &m_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 =
|
|
[&]()
|
|
{
|
|
m_preview.Clear();
|
|
m_view->Update( &m_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->PostAction( ACTIONS::refreshPreview );
|
|
|
|
if( aStartingPoint )
|
|
m_toolMgr->PrimeTool( *aStartingPoint );
|
|
|
|
// 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 = GetClampedCoords(
|
|
grid.BestSnapAnchor( m_controls->GetMousePosition(), m_layer, GRID_GRAPHICS ),
|
|
COORDS_PADDING );
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
|
|
if( evt->IsCancelInteractive() || ( started && evt->IsAction( &ACTIONS::undo ) ) )
|
|
{
|
|
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( bds.GetLineThickness( m_layer ) );
|
|
m_stroke.SetLineStyle( LINE_STYLE::DEFAULT );
|
|
m_stroke.SetColor( COLOR4D::UNSPECIFIED );
|
|
|
|
m_textAttrs.m_Size = bds.GetTextSize( m_layer );
|
|
m_textAttrs.m_StrokeWidth = bds.GetTextThickness( m_layer );
|
|
InferBold( &m_textAttrs );
|
|
m_textAttrs.m_Italic = bds.GetTextItalic( m_layer );
|
|
m_textAttrs.m_KeepUpright = bds.GetTextUpright( m_layer );
|
|
m_textAttrs.m_Mirrored = IsBackLayer( m_layer );
|
|
m_textAttrs.m_Halign = GR_TEXT_H_ALIGN_LEFT;
|
|
m_textAttrs.m_Valign = GR_TEXT_V_ALIGN_TOP;
|
|
}
|
|
|
|
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 );
|
|
|
|
if( PCB_TEXTBOX* pcb_textbox = dynamic_cast<PCB_TEXTBOX*>( graphic ) )
|
|
pcb_textbox->SetAttributes( m_textAttrs );
|
|
|
|
m_view->Update( &m_preview );
|
|
frame()->SetMsgPanel( graphic );
|
|
}
|
|
else
|
|
{
|
|
evt->SetPassEvent();
|
|
}
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
if( !graphic )
|
|
m_toolMgr->VetoContextMenuMouseWarp();
|
|
|
|
m_menu.ShowContextMenu( selection() );
|
|
}
|
|
else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) )
|
|
{
|
|
if( !graphic )
|
|
break;
|
|
|
|
if( !started )
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
if( aStartingPoint )
|
|
{
|
|
cursorPos = *aStartingPoint;
|
|
aStartingPoint = std::nullopt;
|
|
}
|
|
|
|
// Init the new item attributes
|
|
if( graphic ) // always true, but Coverity can't seem to figure that out
|
|
{
|
|
graphic->SetShape( static_cast<SHAPE_T>( shape ) );
|
|
graphic->SetFilled( false );
|
|
graphic->SetStroke( m_stroke );
|
|
graphic->SetLayer( m_layer );
|
|
}
|
|
|
|
if( PCB_TEXTBOX* pcb_textbox = dynamic_cast<PCB_TEXTBOX*>( graphic ) )
|
|
pcb_textbox->SetAttributes( m_textAttrs );
|
|
|
|
grid.SetSkipPoint( cursorPos );
|
|
|
|
twoPointMgr.SetOrigin( cursorPos );
|
|
twoPointMgr.SetEnd( cursorPos );
|
|
|
|
if( !isLocalOriginSet )
|
|
m_frame->GetScreen()->m_LocalOrigin = cursorPos;
|
|
|
|
m_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( twoPointMgr, graphic );
|
|
|
|
started = true;
|
|
}
|
|
else
|
|
{
|
|
PCB_SHAPE* snapItem = dynamic_cast<PCB_SHAPE*>( grid.GetSnapped() );
|
|
|
|
if( shape == SHAPE_T::SEGMENT && snapItem && graphic->GetLength() > 0 )
|
|
{
|
|
// User has clicked on the end of an existing segment, closing a path
|
|
BOARD_COMMIT commit( m_frame );
|
|
|
|
commit.Add( graphic );
|
|
commit.Push( _( "Draw Line" ) );
|
|
m_toolMgr->RunAction<EDA_ITEM*>( PCB_ACTIONS::selectItem, graphic );
|
|
|
|
graphic = nullptr;
|
|
}
|
|
else if( twoPointMgr.IsEmpty() || evt->IsDblClick( BUT_LEFT ) )
|
|
{
|
|
// User has clicked twice in the same spot, meaning we're finished
|
|
delete graphic;
|
|
graphic = nullptr;
|
|
}
|
|
|
|
m_preview.Clear();
|
|
twoPointMgr.Reset();
|
|
break;
|
|
}
|
|
|
|
twoPointMgr.SetEnd( GetClampedCoords( cursorPos ) );
|
|
}
|
|
else if( evt->IsMotion() )
|
|
{
|
|
VECTOR2I clampedCursorPos = cursorPos;
|
|
|
|
if( shape == SHAPE_T::CIRCLE || shape == SHAPE_T::ARC )
|
|
clampedCursorPos = getClampedRadiusEnd( twoPointMgr.GetOrigin(), cursorPos );
|
|
else
|
|
clampedCursorPos = getClampedDifferenceEnd( twoPointMgr.GetOrigin(), cursorPos );
|
|
|
|
// 45 degree lines
|
|
if( started && Is45Limited() )
|
|
{
|
|
const VECTOR2I lineVector( clampedCursorPos - VECTOR2I( twoPointMgr.GetOrigin() ) );
|
|
|
|
// get a restricted 45/H/V line from the last fixed point to the cursor
|
|
VECTOR2I newEnd = GetVectorSnapped45( lineVector, ( shape == SHAPE_T::RECTANGLE ) );
|
|
m_controls->ForceCursorPosition( true, VECTOR2I( twoPointMgr.GetEnd() ) );
|
|
twoPointMgr.SetEnd( twoPointMgr.GetOrigin() + newEnd );
|
|
twoPointMgr.SetAngleSnap( true );
|
|
}
|
|
else
|
|
{
|
|
twoPointMgr.SetEnd( clampedCursorPos );
|
|
twoPointMgr.SetAngleSnap( false );
|
|
}
|
|
|
|
updateSegmentFromGeometryMgr( twoPointMgr, graphic );
|
|
m_view->Update( &m_preview );
|
|
m_view->Update( &twoPointAsst );
|
|
}
|
|
else if( started && ( evt->IsAction( &PCB_ACTIONS::doDelete )
|
|
|| evt->IsAction( &PCB_ACTIONS::deleteLastPoint ) ) )
|
|
{
|
|
if( aCommittedGraphics && !aCommittedGraphics->empty() )
|
|
{
|
|
twoPointMgr.SetOrigin( aCommittedGraphics->top()->GetStart() );
|
|
twoPointMgr.SetEnd( aCommittedGraphics->top()->GetEnd() );
|
|
aCommittedGraphics->pop();
|
|
|
|
getViewControls()->WarpMouseCursor( twoPointMgr.GetEnd(), true );
|
|
|
|
if( PICKED_ITEMS_LIST* undo = m_frame->PopCommandFromUndoList() )
|
|
{
|
|
m_frame->PutDataInPreviousState( undo );
|
|
m_frame->ClearListAndDeleteItems( undo );
|
|
delete undo;
|
|
}
|
|
|
|
updateSegmentFromGeometryMgr( twoPointMgr, graphic );
|
|
m_view->Update( &m_preview );
|
|
m_view->Update( &twoPointAsst );
|
|
}
|
|
else
|
|
{
|
|
cleanup();
|
|
break;
|
|
}
|
|
}
|
|
else if( graphic && evt->IsAction( &PCB_ACTIONS::incWidth ) )
|
|
{
|
|
m_stroke.SetWidth( m_stroke.GetWidth() + WIDTH_STEP );
|
|
graphic->SetStroke( m_stroke );
|
|
m_view->Update( &m_preview );
|
|
frame()->SetMsgPanel( graphic );
|
|
}
|
|
else if( graphic && 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( &m_preview );
|
|
frame()->SetMsgPanel( graphic );
|
|
}
|
|
}
|
|
else if( started && evt->IsAction( &PCB_ACTIONS::properties ) )
|
|
{
|
|
frame()->OnEditItemRequest( graphic );
|
|
m_view->Update( &m_preview );
|
|
frame()->SetMsgPanel( graphic );
|
|
break;
|
|
}
|
|
else if( started && ( ZONE_FILLER_TOOL::IsZoneFillAction( evt )
|
|
|| evt->IsAction( &ACTIONS::redo ) ) )
|
|
{
|
|
wxBell();
|
|
}
|
|
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( &m_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 );
|
|
|
|
if( aMgr.GetSubtended() < ANGLE_0 )
|
|
{
|
|
vec = aMgr.GetStartRadiusEnd();
|
|
aArc.SetStart( vec );
|
|
vec = aMgr.GetEndRadiusEnd();
|
|
aArc.SetEnd( vec );
|
|
}
|
|
else
|
|
{
|
|
vec = aMgr.GetEndRadiusEnd();
|
|
aArc.SetStart( vec );
|
|
vec = aMgr.GetStartRadiusEnd();
|
|
aArc.SetEnd( vec );
|
|
}
|
|
}
|
|
|
|
|
|
bool DRAWING_TOOL::drawArc( const TOOL_EVENT& aTool, PCB_SHAPE** aGraphic,
|
|
std::optional<VECTOR2D> aStartingPoint )
|
|
{
|
|
wxCHECK( aGraphic, false );
|
|
|
|
PCB_SHAPE*& graphic = *aGraphic;
|
|
|
|
wxCHECK( graphic, false );
|
|
|
|
if( m_layer != m_frame->GetActiveLayer() )
|
|
{
|
|
m_layer = m_frame->GetActiveLayer();
|
|
m_stroke.SetWidth( m_frame->GetDesignSettings().GetLineThickness( m_layer ) );
|
|
m_stroke.SetLineStyle( LINE_STYLE::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, pcbIUScale, 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 started = false;
|
|
bool cancelled = false;
|
|
|
|
m_toolMgr->PostAction( ACTIONS::refreshPreview );
|
|
|
|
if( aStartingPoint )
|
|
m_toolMgr->PrimeTool( *aStartingPoint );
|
|
|
|
// Main loop: keep receiving events
|
|
while( TOOL_EVENT* evt = Wait() )
|
|
{
|
|
if( started )
|
|
m_frame->SetMsgPanel( graphic );
|
|
|
|
setCursor();
|
|
|
|
graphic->SetLayer( m_layer );
|
|
|
|
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
|
|
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
|
|
VECTOR2I cursorPos = GetClampedCoords(
|
|
grid.BestSnapAnchor( m_controls->GetMousePosition(), graphic, GRID_GRAPHICS ),
|
|
COORDS_PADDING );
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
|
|
if( evt->IsCancelInteractive() || ( started && evt->IsAction( &ACTIONS::undo ) ) )
|
|
{
|
|
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->IsClick( BUT_LEFT ) )
|
|
{
|
|
if( !started )
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
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 );
|
|
started = 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( m_frame->GetDesignSettings().GetLineThickness( m_layer ) );
|
|
m_stroke.SetLineStyle( LINE_STYLE::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 ) )
|
|
{
|
|
if( !graphic )
|
|
m_toolMgr->VetoContextMenuMouseWarp();
|
|
|
|
m_menu.ShowContextMenu( selection() );
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::incWidth ) )
|
|
{
|
|
m_stroke.SetWidth( m_stroke.GetWidth() + WIDTH_STEP );
|
|
|
|
if( graphic )
|
|
{
|
|
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 );
|
|
|
|
if( graphic )
|
|
{
|
|
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 if( started && ( ZONE_FILLER_TOOL::IsZoneFillAction( evt )
|
|
|| evt->IsAction( &ACTIONS::redo ) ) )
|
|
{
|
|
wxBell();
|
|
}
|
|
else
|
|
{
|
|
evt->SetPassEvent();
|
|
}
|
|
|
|
if( arcManager.IsComplete() )
|
|
{
|
|
break;
|
|
}
|
|
else if( arcManager.HasGeometryChanged() )
|
|
{
|
|
updateArcFromConstructionMgr( arcManager, *graphic );
|
|
m_view->Update( &preview );
|
|
m_view->Update( &arcAsst );
|
|
|
|
if( started )
|
|
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 );
|
|
}
|
|
|
|
// we want a single zone
|
|
if( selection.Size() == 1 && selection[0]->Type() == PCB_ZONE_T )
|
|
*aZone = static_cast<ZONE*>( selection[0] );
|
|
|
|
// expected a zone, but didn't get one
|
|
if( !*aZone )
|
|
{
|
|
if( clearSelection )
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
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;
|
|
params.m_layer = m_frame->GetActiveLayer();
|
|
|
|
if( zoneMode == ZONE_MODE::SIMILAR && !sourceZone->IsOnLayer( params.m_layer ) )
|
|
params.m_layer = sourceZone->GetFirstLayer();
|
|
|
|
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() );
|
|
|
|
m_frame->PushTool( aEvent );
|
|
|
|
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 = GetClampedCoords( grid.BestSnapAnchor( cursorPos, layers, GRID_GRAPHICS ),
|
|
COORDS_PADDING );
|
|
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
|
|
polyGeomMgr.SetLeaderMode( Is45Limited() ? POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45
|
|
: POLYGON_GEOM_MANAGER::LEADER_MODE::DIRECT );
|
|
|
|
if( evt->IsCancelInteractive() )
|
|
{
|
|
if( started )
|
|
{
|
|
cleanup();
|
|
}
|
|
else
|
|
{
|
|
m_frame->PopTool( aEvent );
|
|
|
|
// We've handled the cancel event. Don't cancel other tools
|
|
evt->SetPassEvent( false );
|
|
break;
|
|
}
|
|
}
|
|
else if( evt->IsActivate() )
|
|
{
|
|
if( started )
|
|
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( aEvent );
|
|
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 ) )
|
|
{
|
|
if( !started )
|
|
m_toolMgr->VetoContextMenuMouseWarp();
|
|
|
|
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( started && ( evt->IsAction( &PCB_ACTIONS::deleteLastPoint )
|
|
|| evt->IsAction( &ACTIONS::doDelete )
|
|
|| evt->IsAction( &ACTIONS::undo ) ) )
|
|
{
|
|
if( std::optional<VECTOR2I> last = polyGeomMgr.DeleteLastCorner() )
|
|
{
|
|
cursorPos = last.value();
|
|
getViewControls()->WarpMouseCursor( cursorPos, true );
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
polyGeomMgr.SetCursorPosition( cursorPos );
|
|
}
|
|
else
|
|
{
|
|
cleanup();
|
|
}
|
|
}
|
|
else if( started && ( evt->IsMotion()
|
|
|| evt->IsDrag( BUT_LEFT ) ) )
|
|
{
|
|
polyGeomMgr.SetCursorPosition( cursorPos );
|
|
}
|
|
else if( started && ( ZONE_FILLER_TOOL::IsZoneFillAction( evt )
|
|
|| evt->IsAction( &ACTIONS::redo ) ) )
|
|
{
|
|
wxBell();
|
|
}
|
|
else if( started && evt->IsAction( &PCB_ACTIONS::properties ) )
|
|
{
|
|
frame()->OnEditItemRequest( zoneTool.GetZone() );
|
|
zoneTool.OnGeometryChange( polyGeomMgr );
|
|
frame()->SetMsgPanel( zoneTool.GetZone() );
|
|
}
|
|
/*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>();
|
|
|
|
if( router )
|
|
m_allowDRCViolations = router->Router()->Settings().AllowDRCViolations();
|
|
|
|
try
|
|
{
|
|
if( aFrame )
|
|
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() )
|
|
{
|
|
std::optional<int> padOverride = pad->GetClearanceOverrides( nullptr );
|
|
|
|
if( padOverride.has_value() )
|
|
m_worstClearance = std::max( m_worstClearance, padOverride.value() );
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
|
|
wxCHECK( view, nullptr );
|
|
|
|
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( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T )
|
|
{
|
|
PCB_TRACK* track = static_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 )
|
|
{
|
|
DRC_CONSTRAINT constraint;
|
|
int clearance;
|
|
BOARD_CONNECTED_ITEM* connectedItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( aOther );
|
|
ZONE* zone = dynamic_cast<ZONE*>( aOther );
|
|
|
|
if( zone && zone->GetIsRuleArea() )
|
|
{
|
|
if( zone->GetDoNotAllowVias() )
|
|
return zone->Outline()->Collide( aVia->GetPosition(), aVia->GetWidth() / 2 );
|
|
|
|
return false;
|
|
}
|
|
|
|
if( connectedItem )
|
|
{
|
|
int connectedItemNet = connectedItem->GetNetCode();
|
|
|
|
if( connectedItemNet == 0 || connectedItemNet == aVia->GetNetCode() )
|
|
return false;
|
|
}
|
|
|
|
for( PCB_LAYER_ID layer : aOther->GetLayerSet().Seq() )
|
|
{
|
|
// Reference images are "on" a copper layer but are not actually part of it
|
|
if( !IsCopperLayer( layer ) || aOther->Type() == PCB_REFERENCE_IMAGE_T )
|
|
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;
|
|
}
|
|
}
|
|
|
|
if( aOther->HasHole() )
|
|
{
|
|
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( aOther->GetEffectiveHoleShape().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 && !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_FIELD_T || item->Type() == PCB_TEXT_T )
|
|
&& !static_cast<PCB_TEXT*>( item )->IsVisible() )
|
|
{
|
|
continue; // ignore hidden items
|
|
}
|
|
else if( checkedItems.count( item ) )
|
|
{
|
|
continue; // already checked
|
|
}
|
|
|
|
if( hasDRCViolation( aVia, item ) )
|
|
return true;
|
|
|
|
checkedItems.insert( item );
|
|
}
|
|
|
|
DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( DISALLOW_CONSTRAINT, aVia, nullptr,
|
|
UNDEFINED_LAYER );
|
|
|
|
if( constraint.m_DisallowFlags && constraint.GetSeverity() != RPT_SEVERITY_IGNORE )
|
|
return true;
|
|
|
|
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 selectPossibleNetsByPopMenu( std::list<int>& aNetcodeList )
|
|
{
|
|
ACTION_MENU menu( true );
|
|
const NETINFO_LIST& netInfo = m_board->GetNetInfo();
|
|
std::map<int, int> menuIDNetCodeMap;
|
|
int menuID = 1;
|
|
|
|
for( int netcode : aNetcodeList )
|
|
{
|
|
wxString menuText;
|
|
if( menuID < 10 )
|
|
{
|
|
#ifdef __WXMAC__
|
|
menuText = wxString::Format( "%s\t",
|
|
netInfo.GetNetItem( netcode )->GetNetname() );
|
|
#else
|
|
menuText = wxString::Format( "&%d %s\t",
|
|
menuID,
|
|
netInfo.GetNetItem( netcode )->GetNetname() );
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
menuText = netInfo.GetNetItem( netcode )->GetNetname();
|
|
}
|
|
|
|
menu.Add( menuText, menuID, BITMAPS::INVALID_BITMAP );
|
|
menuIDNetCodeMap[ menuID ] = netcode;
|
|
menuID++;
|
|
}
|
|
|
|
menu.SetTitle( _( "Select Net:" ) );
|
|
menu.DisplayTitle( true );
|
|
|
|
DRAWING_TOOL* drawingTool = m_frame->GetToolManager()->GetTool<DRAWING_TOOL>();
|
|
drawingTool->SetContextMenu( &menu, CMENU_NOW );
|
|
|
|
int selectNetCode = -1;
|
|
while( TOOL_EVENT* evt = drawingTool->Wait() )
|
|
{
|
|
if( evt->Action() == TA_CHOICE_MENU_UPDATE )
|
|
{
|
|
evt->SetPassEvent();
|
|
}
|
|
else if( evt->Action() == TA_CHOICE_MENU_CHOICE )
|
|
{
|
|
std::optional<int> id = evt->GetCommandId();
|
|
|
|
// User has selected an item, so this one will be returned
|
|
if( id && ( *id > 0 ) && ( *id < menuID ) )
|
|
{
|
|
selectNetCode = menuIDNetCodeMap.at( *id );
|
|
}
|
|
// User has cancelled the menu (either by <esc> or clicking out of it),
|
|
// so different from -1, we return -2 to cancel the via placement
|
|
// but stay in the via tool
|
|
else
|
|
{
|
|
selectNetCode = -2;
|
|
}
|
|
}
|
|
else if( evt->Action() == TA_CHOICE_MENU_CLOSED )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return selectNetCode;
|
|
}
|
|
|
|
int findStitchedZoneNet( PCB_VIA* aVia )
|
|
{
|
|
const VECTOR2I position = aVia->GetPosition();
|
|
const LSET lset = aVia->GetLayerSet();
|
|
PCB_DISPLAY_OPTIONS opts = m_frame->GetDisplayOptions();
|
|
bool highContrast = ( opts.m_ContrastModeDisplay != HIGH_CONTRAST_MODE::NORMAL );
|
|
|
|
// In high contrast mode, it should be treated as the only one visible layer
|
|
// We just return the net of active layer (not the visible layers) without show menu
|
|
if( highContrast )
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LSET tempLset = LSET( m_board->GetVisibleLayers() & lset );
|
|
std::list<int> netcodeList;
|
|
|
|
// When there is only one visible layer and the others invisible,
|
|
// we find net in the only visible layer instead of showing menu
|
|
if( 1 != tempLset.Seq().size() )
|
|
{
|
|
tempLset = lset;
|
|
}
|
|
|
|
// get possible nets from layers
|
|
// and store them in netcodelist
|
|
for( ZONE* z : m_board->Zones() )
|
|
{
|
|
for( PCB_LAYER_ID layer : tempLset.Seq() )
|
|
{
|
|
if( z->IsOnLayer( layer ) )
|
|
{
|
|
if( z->HitTestFilledArea( layer, position ) )
|
|
netcodeList.push_back( z->GetNetCode() );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
netcodeList.sort();
|
|
netcodeList.unique();
|
|
|
|
if( netcodeList.size() == 1 )
|
|
{
|
|
return netcodeList.front();
|
|
}
|
|
|
|
// When there are more than one possible net , it's ambiguous
|
|
// So show a pop-up menu of possible nets
|
|
if( netcodeList.size() > 1 )
|
|
{
|
|
return selectPossibleNetsByPopMenu( netcodeList );
|
|
}
|
|
}
|
|
|
|
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() );
|
|
via->SetIsFree( false );
|
|
}
|
|
else if( pad )
|
|
{
|
|
via->SetNetCode( pad->GetNetCode() );
|
|
via->SetIsFree( false );
|
|
}
|
|
else
|
|
{
|
|
int netcode = findStitchedZoneNet( via );
|
|
|
|
// -2 signifies that the user has canceled the placement
|
|
// of the via while remaining in the via tool
|
|
if( -2 == netcode )
|
|
return false;
|
|
|
|
via->SetNetCode( netcode );
|
|
via->SetIsFree( via->GetNetCode() > 0 );
|
|
}
|
|
|
|
if( checkDRCViolation( via ) )
|
|
{
|
|
m_frame->ShowInfoBarError( _( "Via location violates DRC." ), true,
|
|
WX_INFOBAR::MESSAGE_TYPE::DRC_VIOLATION );
|
|
|
|
if( !m_allowDRCViolations )
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if( infobar->GetMessageType() == WX_INFOBAR::MESSAGE_TYPE::DRC_VIOLATION )
|
|
infobar->Dismiss();
|
|
}
|
|
|
|
aCommit.Add( via );
|
|
|
|
// If the user explicitly disables snap (using shift), then don't break the tracks.
|
|
// This will prevent PNS from being able to connect the via and track but
|
|
// it is explicitly requested by the user
|
|
if( track && m_gridHelper.GetSnap() )
|
|
{
|
|
VECTOR2I trackStart = track->GetStart();
|
|
VECTOR2I trackEnd = track->GetEnd();
|
|
SEG trackSeg( trackStart, trackEnd );
|
|
|
|
if( viaPos == trackStart || viaPos == trackEnd )
|
|
return true;
|
|
|
|
if( !trackSeg.Contains( viaPos ) )
|
|
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->GetEffectiveNetClass();
|
|
|
|
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, &placer, _( "Place via" ), IPO_REPEAT | IPO_SINGLE_CLICK );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
const unsigned int DRAWING_TOOL::WIDTH_STEP = pcbIUScale.mmToIU( 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::PlaceReferenceImage, PCB_ACTIONS::placeReferenceImage.MakeEvent() );
|
|
Go( &DRAWING_TOOL::PlaceText, PCB_ACTIONS::placeText.MakeEvent() );
|
|
Go( &DRAWING_TOOL::DrawTable, PCB_ACTIONS::drawTable.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() );
|
|
|
|
Go( &DRAWING_TOOL::PlaceTuningPattern, PCB_ACTIONS::tuneSingleTrack.MakeEvent() );
|
|
Go( &DRAWING_TOOL::PlaceTuningPattern, PCB_ACTIONS::tuneDiffPair.MakeEvent() );
|
|
Go( &DRAWING_TOOL::PlaceTuningPattern, PCB_ACTIONS::tuneSkew.MakeEvent() );
|
|
}
|