1882 lines
61 KiB
C++
1882 lines
61 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2014-2017 CERN
|
|
* Copyright (C) 2018-2019 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 "pcb_actions.h"
|
|
|
|
#include <pcb_edit_frame.h>
|
|
#include <class_draw_panel_gal.h>
|
|
#include <project.h>
|
|
#include <id.h>
|
|
#include <pcbnew_id.h>
|
|
#include <confirm.h>
|
|
#include <import_gfx/dialog_import_gfx.h>
|
|
|
|
#include <view/view_group.h>
|
|
#include <view/view_controls.h>
|
|
#include <view/view.h>
|
|
#include <gal/graphics_abstraction_layer.h>
|
|
#include <tool/tool_manager.h>
|
|
#include <geometry/geometry_utils.h>
|
|
#include <ratsnest_data.h>
|
|
#include <board_commit.h>
|
|
#include <scoped_set_reset.h>
|
|
#include <bitmaps.h>
|
|
#include <painter.h>
|
|
#include <status_popup.h>
|
|
#include "grid_helper.h"
|
|
#include <dialogs/dialog_text_properties.h>
|
|
#include <preview_items/arc_assistant.h>
|
|
|
|
#include <class_board.h>
|
|
#include <class_edge_mod.h>
|
|
#include <class_pcb_text.h>
|
|
#include <class_dimension.h>
|
|
#include <class_zone.h>
|
|
#include <class_module.h>
|
|
|
|
#include <tools/selection_tool.h>
|
|
#include <tools/tool_event_utils.h>
|
|
#include <tools/zone_create_helper.h>
|
|
|
|
using SCOPED_DRAW_MODE = SCOPED_SET_RESET<DRAWING_TOOL::MODE>;
|
|
|
|
// Drawing tool actions
|
|
TOOL_ACTION PCB_ACTIONS::drawLine( "pcbnew.InteractiveDrawing.line",
|
|
AS_GLOBAL,
|
|
MD_SHIFT + MD_CTRL + 'L', LEGACY_HK_NAME( "Draw Line" ),
|
|
_( "Draw Line" ), _( "Draw a line" ),
|
|
add_graphical_segments_xpm, AF_ACTIVATE );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::drawPolygon( "pcbnew.InteractiveDrawing.graphicPolygon",
|
|
AS_GLOBAL,
|
|
MD_SHIFT + MD_CTRL + 'P', LEGACY_HK_NAME( "Draw Graphic Polygon" ),
|
|
_( "Draw Graphic Polygon" ), _( "Draw a graphic polygon" ),
|
|
add_graphical_polygon_xpm, AF_ACTIVATE );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::drawCircle( "pcbnew.InteractiveDrawing.circle",
|
|
AS_GLOBAL,
|
|
MD_SHIFT + MD_CTRL + 'C', LEGACY_HK_NAME( "Draw Circle" ),
|
|
_( "Draw Circle" ), _( "Draw a circle" ),
|
|
add_circle_xpm, AF_ACTIVATE );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::drawArc( "pcbnew.InteractiveDrawing.arc",
|
|
AS_GLOBAL,
|
|
MD_SHIFT + MD_CTRL + 'A', LEGACY_HK_NAME( "Draw Arc" ),
|
|
_( "Draw Arc" ), _( "Draw an arc" ),
|
|
add_arc_xpm, AF_ACTIVATE );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::placeText( "pcbnew.InteractiveDrawing.text",
|
|
AS_GLOBAL,
|
|
MD_SHIFT + MD_CTRL + 'T', LEGACY_HK_NAME( "Add Text" ),
|
|
_( "Add Text" ), _( "Add a text item" ),
|
|
text_xpm, AF_ACTIVATE );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::drawDimension( "pcbnew.InteractiveDrawing.dimension",
|
|
AS_GLOBAL,
|
|
MD_SHIFT + MD_CTRL + 'H', LEGACY_HK_NAME( "Add Dimension" ),
|
|
_( "Add Dimension" ), _( "Add a dimension" ),
|
|
add_dimension_xpm, AF_ACTIVATE );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::drawZone( "pcbnew.InteractiveDrawing.zone",
|
|
AS_GLOBAL,
|
|
#ifdef __WXOSX_MAC__
|
|
GR_KB_ALT + 'Z',
|
|
#else
|
|
MD_SHIFT + MD_CTRL + 'Z',
|
|
#endif
|
|
LEGACY_HK_NAME( "Add Filled Zone" ),
|
|
_( "Add Filled Zone" ), _( "Add a filled zone" ),
|
|
add_zone_xpm, AF_ACTIVATE );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::drawVia( "pcbnew.InteractiveDrawing.via",
|
|
AS_GLOBAL,
|
|
MD_SHIFT + MD_CTRL + 'V', LEGACY_HK_NAME( "Add Vias" ),
|
|
_( "Add Vias" ), _( "Add free-standing vias" ),
|
|
add_via_xpm, AF_ACTIVATE );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::drawZoneKeepout( "pcbnew.InteractiveDrawing.keepout",
|
|
AS_GLOBAL,
|
|
MD_SHIFT + MD_CTRL + 'K', LEGACY_HK_NAME( "Add Keepout Area" ),
|
|
_( "Add Keepout Area" ), _( "Add a keepout area" ),
|
|
add_keepout_area_xpm, AF_ACTIVATE );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::drawZoneCutout( "pcbnew.InteractiveDrawing.zoneCutout",
|
|
AS_GLOBAL,
|
|
MD_SHIFT + 'C', LEGACY_HK_NAME( "Add a Zone Cutout" ),
|
|
_( "Add a Zone Cutout" ), _( "Add a cutout area of an existing zone" ),
|
|
add_zone_cutout_xpm, AF_ACTIVATE );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::drawSimilarZone( "pcbnew.InteractiveDrawing.similarZone",
|
|
AS_GLOBAL,
|
|
MD_SHIFT + MD_CTRL + '.', LEGACY_HK_NAME( "Add a Similar Zone" ),
|
|
_( "Add a Similar Zone" ), _( "Add a zone with the same settings as an existing zone" ),
|
|
add_zone_xpm, AF_ACTIVATE );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::placeImportedGraphics( "pcbnew.InteractiveDrawing.placeImportedGraphics",
|
|
AS_GLOBAL,
|
|
MD_SHIFT + MD_CTRL + 'F', LEGACY_HK_NAME( "Place DXF" ),
|
|
_( "Place Imported Graphics" ), "",
|
|
import_vector_xpm, AF_ACTIVATE );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::setAnchor( "pcbnew.InteractiveDrawing.setAnchor",
|
|
AS_GLOBAL,
|
|
MD_SHIFT + MD_CTRL + 'N', LEGACY_HK_NAME( "Place the Footprint Anchor" ),
|
|
_( "Place the Footprint Anchor" ), "",
|
|
anchor_xpm, AF_ACTIVATE );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::incWidth( "pcbnew.InteractiveDrawing.incWidth",
|
|
AS_CONTEXT,
|
|
MD_CTRL + '+', LEGACY_HK_NAME( "Increase Line Width" ),
|
|
_( "Increase Line Width" ), _( "Increase the line width" ) );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::decWidth( "pcbnew.InteractiveDrawing.decWidth",
|
|
AS_CONTEXT,
|
|
MD_CTRL + '-', LEGACY_HK_NAME( "Decrease Line Width" ),
|
|
_( "Decrease Line Width" ), _( "Decrease the line width" ) );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::arcPosture( "pcbnew.InteractiveDrawing.arcPosture",
|
|
AS_CONTEXT,
|
|
'/', LEGACY_HK_NAME( "Switch Track Posture" ),
|
|
_( "Switch Arc Posture" ), _( "Switch the arc posture" ) );
|
|
|
|
/*
|
|
* Contextual actions
|
|
*/
|
|
|
|
static TOOL_ACTION deleteLastPoint( "pcbnew.InteractiveDrawing.deleteLastPoint",
|
|
AS_CONTEXT,
|
|
WXK_BACK, "",
|
|
_( "Delete Last Point" ), _( "Delete the last point added to the current item" ),
|
|
undo_xpm );
|
|
|
|
static TOOL_ACTION closeZoneOutline( "pcbnew.InteractiveDrawing.closeZoneOutline",
|
|
AS_CONTEXT, 0, "",
|
|
_( "Close Zone Outline" ), _( "Close the outline of a zone in progress" ),
|
|
checked_ok_xpm );
|
|
|
|
|
|
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_lineWidth( 1 )
|
|
{
|
|
}
|
|
|
|
|
|
DRAWING_TOOL::~DRAWING_TOOL()
|
|
{
|
|
}
|
|
|
|
|
|
bool DRAWING_TOOL::Init()
|
|
{
|
|
auto activeToolFunctor = [ this ] ( const SELECTION& aSel ) {
|
|
return m_mode != MODE::NONE;
|
|
};
|
|
|
|
// some interactive drawing tools can undo the last point
|
|
auto canUndoPoint = [ this ] ( const SELECTION& aSel ) {
|
|
return m_mode == MODE::ARC || m_mode == MODE::ZONE;
|
|
};
|
|
|
|
// functor for zone-only actions
|
|
auto zoneActiveFunctor = [this ] ( const SELECTION& aSel ) {
|
|
return m_mode == MODE::ZONE;
|
|
};
|
|
|
|
auto& ctxMenu = m_menu.GetMenu();
|
|
|
|
// cancel current tool goes in main context menu at the top if present
|
|
ctxMenu.AddItem( ACTIONS::cancelInteractive, activeToolFunctor, 1 );
|
|
ctxMenu.AddSeparator( activeToolFunctor, 1 );
|
|
|
|
// tool-specific actions
|
|
ctxMenu.AddItem( closeZoneOutline, zoneActiveFunctor, 200 );
|
|
ctxMenu.AddItem( deleteLastPoint, canUndoPoint, 200 );
|
|
|
|
ctxMenu.AddSeparator( canUndoPoint, 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>();
|
|
}
|
|
|
|
|
|
DRAWING_TOOL::MODE DRAWING_TOOL::GetDrawingMode() const
|
|
{
|
|
return m_mode;
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::DrawLine( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( m_editModules && !m_frame->GetModel() )
|
|
return 0;
|
|
|
|
BOARD_ITEM_CONTAINER* parent = m_frame->GetModel();
|
|
DRAWSEGMENT* line = m_editModules ? new EDGE_MODULE( (MODULE*) parent ) : new DRAWSEGMENT;
|
|
|
|
auto startingPoint = boost::make_optional<VECTOR2D>( false, VECTOR2D( 0, 0 ) );
|
|
BOARD_COMMIT commit( m_frame );
|
|
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::LINE );
|
|
|
|
m_frame->SetToolID( m_editModules ? ID_MODEDIT_LINE_TOOL : ID_PCB_ADD_LINE_BUTT,
|
|
wxCURSOR_PENCIL, _( "Add graphic line" ) );
|
|
|
|
while( drawSegment( S_SEGMENT, line, startingPoint ) )
|
|
{
|
|
// This can be reset by some actions (e.g. Save Board), so ensure it stays set.
|
|
m_frame->GetGalCanvas()->SetCurrentCursor( wxCURSOR_PENCIL );
|
|
|
|
if( line )
|
|
{
|
|
if( m_editModules )
|
|
static_cast<EDGE_MODULE*>( line )->SetLocalCoord();
|
|
|
|
commit.Add( line );
|
|
commit.Push( _( "Draw a line segment" ) );
|
|
startingPoint = VECTOR2D( line->GetEnd() );
|
|
}
|
|
else
|
|
{
|
|
startingPoint = NULLOPT;
|
|
}
|
|
|
|
line = m_editModules ? new EDGE_MODULE( (MODULE*) parent ) : new DRAWSEGMENT;
|
|
}
|
|
|
|
m_frame->SetNoToolSelected();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::DrawCircle( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( m_editModules && !m_frame->GetModel() )
|
|
return 0;
|
|
|
|
BOARD_ITEM_CONTAINER* parent = m_frame->GetModel();
|
|
DRAWSEGMENT* circle = m_editModules ? new EDGE_MODULE( (MODULE*) parent ) : new DRAWSEGMENT;
|
|
BOARD_COMMIT commit( m_frame );
|
|
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::CIRCLE );
|
|
|
|
m_frame->SetToolID( m_editModules ? ID_MODEDIT_CIRCLE_TOOL : ID_PCB_CIRCLE_BUTT,
|
|
wxCURSOR_PENCIL, _( "Add graphic circle" ) );
|
|
|
|
while( drawSegment( S_CIRCLE, circle ) )
|
|
{
|
|
// This can be reset by some actions (e.g. Save Board), so ensure it stays set.
|
|
m_frame->GetGalCanvas()->SetCurrentCursor( wxCURSOR_PENCIL );
|
|
|
|
if( circle )
|
|
{
|
|
if( m_editModules )
|
|
static_cast<EDGE_MODULE*>( circle )->SetLocalCoord();
|
|
|
|
commit.Add( circle );
|
|
commit.Push( _( "Draw a circle" ) );
|
|
}
|
|
|
|
circle = m_editModules ? new EDGE_MODULE( (MODULE*) parent ) : new DRAWSEGMENT;
|
|
}
|
|
|
|
m_frame->SetNoToolSelected();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::DrawArc( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( m_editModules && !m_frame->GetModel() )
|
|
return 0;
|
|
|
|
BOARD_ITEM_CONTAINER* parent = m_frame->GetModel();
|
|
DRAWSEGMENT* arc = m_editModules ? new EDGE_MODULE( (MODULE*) parent ) : new DRAWSEGMENT;
|
|
BOARD_COMMIT commit( m_frame );
|
|
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::ARC );
|
|
|
|
m_frame->SetToolID( m_editModules ? ID_MODEDIT_ARC_TOOL : ID_PCB_ARC_BUTT,
|
|
wxCURSOR_PENCIL, _( "Add graphic arc" ) );
|
|
|
|
while( drawArc( arc ) )
|
|
{
|
|
// This can be reset by some actions (e.g. Save Board), so ensure it stays set.
|
|
m_frame->GetGalCanvas()->SetCurrentCursor( wxCURSOR_PENCIL );
|
|
|
|
if( arc )
|
|
{
|
|
if( m_editModules )
|
|
static_cast<EDGE_MODULE*>( arc )->SetLocalCoord();
|
|
|
|
commit.Add( arc );
|
|
commit.Push( _( "Draw an arc" ) );
|
|
}
|
|
|
|
arc = m_editModules ? new EDGE_MODULE( (MODULE*) parent ) : new DRAWSEGMENT;
|
|
}
|
|
|
|
m_frame->SetNoToolSelected();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::PlaceText( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( m_editModules && !m_frame->GetModel() )
|
|
return 0;
|
|
|
|
BOARD_ITEM* text = NULL;
|
|
const BOARD_DESIGN_SETTINGS& dsnSettings = m_frame->GetDesignSettings();
|
|
BOARD_COMMIT commit( m_frame );
|
|
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
|
|
m_controls->ShowCursor( true );
|
|
m_controls->SetSnapping( true );
|
|
// do not capture or auto-pan until we start placing some text
|
|
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::TEXT );
|
|
|
|
Activate();
|
|
m_frame->SetToolID( m_editModules ? ID_MODEDIT_TEXT_TOOL : ID_PCB_ADD_TEXT_BUTT,
|
|
wxCURSOR_PENCIL, _( "Add text" ) );
|
|
|
|
bool reselect = false;
|
|
|
|
// Main loop: keep receiving events
|
|
while( OPT_TOOL_EVENT evt = Wait() )
|
|
{
|
|
// This can be reset by some actions (e.g. Save Board), so ensure it stays set.
|
|
m_frame->GetGalCanvas()->SetCurrentCursor( wxCURSOR_PENCIL );
|
|
VECTOR2I cursorPos = m_controls->GetCursorPosition();
|
|
|
|
if( reselect && text )
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, text );
|
|
|
|
if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
|
|
{
|
|
if( text )
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
|
|
|
|
// Delete the old text and have another try
|
|
delete text;
|
|
text = NULL;
|
|
|
|
m_controls->SetAutoPan( false );
|
|
m_controls->CaptureCursor( false );
|
|
m_controls->ShowCursor( true );
|
|
}
|
|
else
|
|
break;
|
|
|
|
if( evt->IsActivate() ) // now finish unconditionally
|
|
break;
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
m_menu.ShowContextMenu( selection() );
|
|
}
|
|
else if( evt->IsClick( BUT_LEFT ) )
|
|
{
|
|
bool placing = text != nullptr;
|
|
|
|
if( !text )
|
|
{
|
|
m_controls->ForceCursorPosition( true, m_controls->GetCursorPosition() );
|
|
PCB_LAYER_ID layer = m_frame->GetActiveLayer();
|
|
|
|
// Init the new item attributes
|
|
if( m_editModules )
|
|
{
|
|
TEXTE_MODULE* textMod = new TEXTE_MODULE( (MODULE*) m_frame->GetModel() );
|
|
|
|
textMod->SetLayer( layer );
|
|
textMod->SetTextSize( dsnSettings.GetTextSize( layer ) );
|
|
textMod->SetThickness( dsnSettings.GetTextThickness( layer ) );
|
|
textMod->SetItalic( dsnSettings.GetTextItalic( layer ) );
|
|
textMod->SetKeepUpright( dsnSettings.GetTextUpright( layer ) );
|
|
textMod->SetTextPos( (wxPoint) cursorPos );
|
|
|
|
text = textMod;
|
|
|
|
DIALOG_TEXT_PROPERTIES textDialog( m_frame, textMod );
|
|
bool cancelled;
|
|
|
|
RunMainStack([&]() {
|
|
cancelled = !textDialog.ShowModal() || textMod->GetText().IsEmpty();
|
|
} );
|
|
|
|
if( cancelled )
|
|
{
|
|
delete text;
|
|
text = nullptr;
|
|
}
|
|
else if( textMod->GetTextPos() != (wxPoint) cursorPos )
|
|
{
|
|
// If the user modified the location then go ahead and place it there.
|
|
// Otherwise we'll drag.
|
|
placing = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TEXTE_PCB* textPcb = new TEXTE_PCB( m_frame->GetModel() );
|
|
// TODO we have to set IS_NEW, otherwise InstallTextPCB.. creates an undo entry :| LEGACY_CLEANUP
|
|
textPcb->SetFlags( IS_NEW );
|
|
|
|
textPcb->SetLayer( layer );
|
|
|
|
// Set the mirrored option for layers on the BACK side of the board
|
|
if( IsBackLayer( layer ) )
|
|
textPcb->SetMirrored( true );
|
|
|
|
textPcb->SetTextSize( dsnSettings.GetTextSize( layer ) );
|
|
textPcb->SetThickness( dsnSettings.GetTextThickness( layer ) );
|
|
textPcb->SetItalic( dsnSettings.GetTextItalic( layer ) );
|
|
textPcb->SetTextPos( (wxPoint) cursorPos );
|
|
|
|
RunMainStack([&]() {
|
|
m_frame->InstallTextOptionsFrame( textPcb );
|
|
} );
|
|
|
|
if( textPcb->GetText().IsEmpty() )
|
|
{
|
|
m_controls->ForceCursorPosition( false );
|
|
delete textPcb;
|
|
}
|
|
else
|
|
text = textPcb;
|
|
}
|
|
|
|
if( text == NULL )
|
|
continue;
|
|
|
|
m_controls->WarpCursor( text->GetPosition(), true );
|
|
m_controls->ForceCursorPosition( false );
|
|
m_controls->CaptureCursor( true );
|
|
m_controls->SetAutoPan( true );
|
|
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, text );
|
|
}
|
|
|
|
if( placing )
|
|
{
|
|
text->ClearFlags();
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
|
|
|
|
commit.Add( text );
|
|
commit.Push( _( "Place a text" ) );
|
|
|
|
m_controls->CaptureCursor( false );
|
|
m_controls->SetAutoPan( false );
|
|
m_controls->ShowCursor( true );
|
|
|
|
text = NULL;
|
|
}
|
|
}
|
|
else if( text && evt->IsMotion() )
|
|
{
|
|
text->SetPosition( (wxPoint) cursorPos );
|
|
selection().SetReferencePoint( cursorPos );
|
|
m_view->Update( &selection() );
|
|
frame()->SetMsgPanel( text );
|
|
}
|
|
|
|
else if( text && evt->IsAction( &PCB_ACTIONS::properties ) )
|
|
{
|
|
// Calling 'Properties' action clears the selection, so we need to restore it
|
|
reselect = true;
|
|
}
|
|
}
|
|
|
|
frame()->SetMsgPanel( board() );
|
|
frame()->SetNoToolSelected();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void DRAWING_TOOL::constrainDimension( DIMENSION* dimension )
|
|
{
|
|
const VECTOR2I lineVector{ dimension->GetEnd() - dimension->GetOrigin() };
|
|
|
|
dimension->SetEnd( wxPoint(
|
|
VECTOR2I( dimension->GetOrigin() ) + GetVectorSnapped45( lineVector ) ) );
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::DrawDimension( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( m_editModules && !m_frame->GetModel() )
|
|
return 0;
|
|
|
|
DIMENSION* dimension = NULL;
|
|
BOARD_COMMIT commit( m_frame );
|
|
GRID_HELPER grid( m_frame );
|
|
|
|
// Add a VIEW_GROUP that serves as a preview for the new item
|
|
PCBNEW_SELECTION preview;
|
|
|
|
m_view->Add( &preview );
|
|
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
|
|
m_controls->ShowCursor( true );
|
|
m_controls->SetSnapping( true );
|
|
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::DIMENSION );
|
|
|
|
Activate();
|
|
m_frame->SetToolID( ID_PCB_DIMENSION_BUTT, wxCURSOR_PENCIL, _( "Add dimension" ) );
|
|
|
|
enum DIMENSION_STEPS
|
|
{
|
|
SET_ORIGIN = 0,
|
|
SET_END,
|
|
SET_HEIGHT,
|
|
FINISHED
|
|
};
|
|
int step = SET_ORIGIN;
|
|
|
|
// Main loop: keep receiving events
|
|
while( OPT_TOOL_EVENT evt = Wait() )
|
|
{
|
|
// This can be reset by some actions (e.g. Save Board), so ensure it stays set.
|
|
m_frame->GetGalCanvas()->SetCurrentCursor( wxCURSOR_PENCIL );
|
|
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
|
|
grid.SetUseGrid( !evt->Modifier( MD_ALT ) );
|
|
m_controls->SetSnapping( !evt->Modifier( MD_ALT ) );
|
|
VECTOR2I cursorPos = grid.BestSnapAnchor( m_controls->GetMousePosition(), nullptr );
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
|
|
if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
|
|
{
|
|
m_controls->SetAutoPan( false );
|
|
|
|
if( step != SET_ORIGIN ) // start from the beginning
|
|
{
|
|
preview.Clear();
|
|
|
|
delete dimension;
|
|
step = SET_ORIGIN;
|
|
}
|
|
else
|
|
break;
|
|
|
|
if( evt->IsActivate() ) // now finish unconditionally
|
|
break;
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::incWidth ) && step != SET_ORIGIN )
|
|
{
|
|
m_lineWidth += WIDTH_STEP;
|
|
dimension->SetWidth( m_lineWidth );
|
|
m_view->Update( &preview );
|
|
frame()->SetMsgPanel( dimension );
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::decWidth ) && step != SET_ORIGIN )
|
|
{
|
|
if( m_lineWidth > WIDTH_STEP )
|
|
{
|
|
m_lineWidth -= WIDTH_STEP;
|
|
dimension->SetWidth( m_lineWidth );
|
|
m_view->Update( &preview );
|
|
frame()->SetMsgPanel( dimension );
|
|
}
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
m_menu.ShowContextMenu( selection() );
|
|
}
|
|
else if( evt->IsClick( BUT_LEFT ) )
|
|
{
|
|
switch( step )
|
|
{
|
|
case SET_ORIGIN:
|
|
{
|
|
PCB_LAYER_ID layer = getDrawingLayer();
|
|
const BOARD_DESIGN_SETTINGS& boardSettings = m_board->GetDesignSettings();
|
|
|
|
if( layer == Edge_Cuts ) // dimensions are not allowed on EdgeCuts
|
|
layer = Dwgs_User;
|
|
|
|
// Init the new item attributes
|
|
dimension = new DIMENSION( m_board );
|
|
dimension->SetLayer( layer );
|
|
dimension->SetOrigin( (wxPoint) cursorPos );
|
|
dimension->SetEnd( (wxPoint) cursorPos );
|
|
dimension->Text().SetTextSize( boardSettings.GetTextSize( layer ) );
|
|
dimension->Text().SetThickness( boardSettings.GetTextThickness( layer ) );
|
|
dimension->Text().SetItalic( boardSettings.GetTextItalic( layer ) );
|
|
dimension->SetWidth( boardSettings.GetLineThickness( layer ) );
|
|
dimension->SetUnits( m_frame->GetUserUnits(), false );
|
|
dimension->AdjustDimensionDetails();
|
|
|
|
preview.Add( dimension );
|
|
frame()->SetMsgPanel( dimension );
|
|
|
|
m_controls->SetAutoPan( true );
|
|
m_controls->CaptureCursor( true );
|
|
}
|
|
break;
|
|
|
|
case SET_END:
|
|
dimension->SetEnd( (wxPoint) cursorPos );
|
|
|
|
if( !!evt->Modifier( MD_CTRL ) )
|
|
constrainDimension( dimension );
|
|
|
|
// Dimensions that have origin and end in the same spot are not valid
|
|
if( dimension->GetOrigin() == dimension->GetEnd() )
|
|
--step;
|
|
|
|
break;
|
|
|
|
case SET_HEIGHT:
|
|
{
|
|
if( (wxPoint) cursorPos != dimension->GetPosition() )
|
|
{
|
|
assert( dimension->GetOrigin() != dimension->GetEnd() );
|
|
assert( dimension->GetWidth() > 0 );
|
|
|
|
preview.Remove( dimension );
|
|
|
|
commit.Add( dimension );
|
|
commit.Push( _( "Draw a dimension" ) );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if( ++step == FINISHED )
|
|
{
|
|
step = SET_ORIGIN;
|
|
m_controls->SetAutoPan( false );
|
|
m_controls->CaptureCursor( false );
|
|
}
|
|
}
|
|
else if( evt->IsMotion() )
|
|
{
|
|
switch( step )
|
|
{
|
|
case SET_END:
|
|
dimension->SetEnd( (wxPoint) cursorPos );
|
|
|
|
if( !!evt->Modifier( MD_CTRL ) )
|
|
constrainDimension( dimension );
|
|
|
|
break;
|
|
|
|
case SET_HEIGHT:
|
|
{
|
|
// Calculating the direction of travel perpendicular to the selected axis
|
|
double angle = dimension->GetAngle() + ( M_PI / 2 );
|
|
|
|
wxPoint delta( (wxPoint) cursorPos - dimension->m_featureLineDO );
|
|
double height = ( delta.x * cos( angle ) ) + ( delta.y * sin( angle ) );
|
|
dimension->SetHeight( height );
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Show a preview of the item
|
|
m_view->Update( &preview );
|
|
if( step )
|
|
frame()->SetMsgPanel( dimension );
|
|
else
|
|
frame()->SetMsgPanel( board() );
|
|
}
|
|
}
|
|
|
|
if( step != SET_ORIGIN )
|
|
delete dimension;
|
|
|
|
m_controls->SetAutoPan( false );
|
|
m_controls->ForceCursorPosition( false );
|
|
|
|
m_view->Remove( &preview );
|
|
frame()->SetMsgPanel( board() );
|
|
m_frame->SetNoToolSelected();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::DrawZone( const TOOL_EVENT& aEvent )
|
|
{
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::ZONE );
|
|
|
|
m_frame->SetToolID( ID_PCB_ZONES_BUTT, wxCURSOR_PENCIL, _( "Add zones" ) );
|
|
|
|
return drawZone( false, ZONE_MODE::ADD );
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::DrawZoneKeepout( const TOOL_EVENT& aEvent )
|
|
{
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::KEEPOUT );
|
|
|
|
m_frame->SetToolID( ID_PCB_KEEPOUT_BUTT, wxCURSOR_PENCIL, _( "Add keepout" ) );
|
|
|
|
return drawZone( true, ZONE_MODE::ADD );
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::DrawZoneCutout( const TOOL_EVENT& aEvent )
|
|
{
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::ZONE );
|
|
|
|
m_frame->SetToolID( ID_PCB_ZONES_BUTT, wxCURSOR_PENCIL, _( "Add zone cutout" ) );
|
|
|
|
return drawZone( false, ZONE_MODE::CUTOUT );
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::DrawGraphicPolygon( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( m_editModules && !m_frame->GetModel() )
|
|
return 0;
|
|
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::GRAPHIC_POLYGON );
|
|
|
|
m_frame->SetToolID( m_editModules ? ID_MODEDIT_POLYGON_TOOL : ID_PCB_ADD_POLYGON_BUTT,
|
|
wxCURSOR_PENCIL, _( "Add graphic polygon" ) );
|
|
|
|
return drawZone( false, ZONE_MODE::GRAPHIC_POLYGON );
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::DrawSimilarZone( const TOOL_EVENT& aEvent )
|
|
{
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::ZONE );
|
|
|
|
m_frame->SetToolID( ID_PCB_ZONES_BUTT, wxCURSOR_PENCIL, _( "Add similar zone" ) );
|
|
|
|
return drawZone( false, ZONE_MODE::SIMILAR );
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::PlaceImportedGraphics( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( !m_frame->GetModel() )
|
|
return 0;
|
|
|
|
// Note: PlaceImportedGraphics() will convert PCB_LINE_T and PCB_TEXT_T to module graphic
|
|
// items if needed
|
|
DIALOG_IMPORT_GFX dlg( m_frame, m_editModules );
|
|
int dlgResult = dlg.ShowModal();
|
|
|
|
auto& 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 to import") );
|
|
return 0;
|
|
}
|
|
|
|
m_frame->SetNoToolSelected();
|
|
|
|
// Add a VIEW_GROUP that serves as a preview for the new item
|
|
PCBNEW_SELECTION preview;
|
|
BOARD_COMMIT commit( m_frame );
|
|
|
|
// Build the undo list & add items to the current view
|
|
for( auto& ptr : list)
|
|
{
|
|
EDA_ITEM* item = ptr.get();
|
|
|
|
if( m_editModules )
|
|
wxASSERT( item->Type() == PCB_MODULE_EDGE_T || item->Type() == PCB_MODULE_TEXT_T );
|
|
else
|
|
wxASSERT( item->Type() == PCB_LINE_T || item->Type() == PCB_TEXT_T );
|
|
|
|
if( dlg.IsPlacementInteractive() )
|
|
preview.Add( item );
|
|
else
|
|
commit.Add( item );
|
|
|
|
ptr.release();
|
|
}
|
|
|
|
if( !dlg.IsPlacementInteractive() )
|
|
{
|
|
commit.Push( _( "Place a DXF_SVG drawing" ) );
|
|
return 0;
|
|
}
|
|
|
|
BOARD_ITEM* firstItem = static_cast<BOARD_ITEM*>( preview.Front() );
|
|
m_view->Add( &preview );
|
|
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
|
|
m_controls->ShowCursor( true );
|
|
m_controls->SetSnapping( true );
|
|
m_controls->ForceCursorPosition( false );
|
|
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::DXF );
|
|
|
|
// Now move the new items to the current cursor position:
|
|
VECTOR2I cursorPos = m_controls->GetCursorPosition();
|
|
VECTOR2I delta = cursorPos - firstItem->GetPosition();
|
|
|
|
for( EDA_ITEM* item : preview )
|
|
static_cast<BOARD_ITEM*>( item )->Move( (wxPoint) delta );
|
|
|
|
m_view->Update( &preview );
|
|
|
|
Activate();
|
|
|
|
// Main loop: keep receiving events
|
|
while( OPT_TOOL_EVENT evt = Wait() )
|
|
{
|
|
cursorPos = m_controls->GetCursorPosition();
|
|
|
|
if( evt->IsMotion() )
|
|
{
|
|
delta = cursorPos - firstItem->GetPosition();
|
|
|
|
for( auto item : preview )
|
|
static_cast<BOARD_ITEM*>( item )->Move( (wxPoint) delta );
|
|
|
|
m_view->Update( &preview );
|
|
}
|
|
else if( evt->Category() == TC_COMMAND )
|
|
{
|
|
// TODO it should be handled by EDIT_TOOL, so add items and select?
|
|
if( TOOL_EVT_UTILS::IsRotateToolEvt( *evt ) )
|
|
{
|
|
const auto rotationPoint = (wxPoint) cursorPos;
|
|
const auto rotationAngle = TOOL_EVT_UTILS::GetEventRotationAngle( *m_frame, *evt );
|
|
|
|
for( auto item : preview )
|
|
static_cast<BOARD_ITEM*>( item )->Rotate( rotationPoint, rotationAngle );
|
|
|
|
m_view->Update( &preview );
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::flip ) )
|
|
{
|
|
for( auto item : preview )
|
|
static_cast<BOARD_ITEM*>( item )->Flip( (wxPoint) cursorPos );
|
|
|
|
m_view->Update( &preview );
|
|
}
|
|
else if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
|
|
{
|
|
preview.FreeItems();
|
|
break;
|
|
}
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
m_menu.ShowContextMenu( selection() );
|
|
}
|
|
else if( evt->IsClick( BUT_LEFT ) )
|
|
{
|
|
// Place the imported drawings
|
|
for( auto item : preview )
|
|
commit.Add( item );
|
|
|
|
commit.Push( _( "Place a DXF_SVG drawing" ) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
preview.Clear();
|
|
m_view->Remove( &preview );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::SetAnchor( const TOOL_EVENT& aEvent )
|
|
{
|
|
assert( m_editModules );
|
|
|
|
if( !m_frame->GetModel() )
|
|
return 0;
|
|
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::ANCHOR );
|
|
|
|
Activate();
|
|
m_frame->SetToolID( ID_MODEDIT_ANCHOR_TOOL, wxCURSOR_PENCIL, _( "Place the footprint anchor" ) );
|
|
|
|
m_controls->ShowCursor( true );
|
|
m_controls->SetSnapping( true );
|
|
m_controls->SetAutoPan( true );
|
|
m_controls->CaptureCursor( false );
|
|
|
|
while( OPT_TOOL_EVENT evt = Wait() )
|
|
{
|
|
// This can be reset by some actions (e.g. Save Board), so ensure it stays set.
|
|
m_frame->GetGalCanvas()->SetCurrentCursor( wxCURSOR_PENCIL );
|
|
|
|
if( evt->IsClick( BUT_LEFT ) )
|
|
{
|
|
MODULE* module = (MODULE*) m_frame->GetModel();
|
|
BOARD_COMMIT commit( m_frame );
|
|
commit.Modify( module );
|
|
|
|
// set the new relative internal local coordinates of footprint items
|
|
VECTOR2I cursorPos = m_controls->GetCursorPosition();
|
|
wxPoint moveVector = module->GetPosition() - (wxPoint) cursorPos;
|
|
module->MoveAnchorPosition( moveVector );
|
|
|
|
commit.Push( _( "Move the footprint reference anchor" ) );
|
|
|
|
// Usually, we do not need to change twice the anchor position,
|
|
// so deselect the active tool
|
|
break;
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
m_menu.ShowContextMenu( selection() );
|
|
}
|
|
else if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
|
|
break;
|
|
}
|
|
|
|
m_frame->SetNoToolSelected();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool DRAWING_TOOL::drawSegment( int aShape, DRAWSEGMENT*& aGraphic, OPT<VECTOR2D> aStartingPoint )
|
|
{
|
|
// Only two shapes are currently supported
|
|
assert( aShape == S_SEGMENT || aShape == S_CIRCLE );
|
|
GRID_HELPER grid( m_frame );
|
|
|
|
m_lineWidth = getSegmentWidth( getDrawingLayer() );
|
|
m_frame->SetActiveLayer( getDrawingLayer() );
|
|
|
|
// Add a VIEW_GROUP that serves as a preview for the new item
|
|
PCBNEW_SELECTION preview;
|
|
m_view->Add( &preview );
|
|
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
|
|
m_controls->ShowCursor( true );
|
|
|
|
Activate();
|
|
|
|
bool direction45 = false; // 45 degrees only mode
|
|
bool started = false;
|
|
bool IsOCurseurSet = ( m_frame->GetScreen()->m_O_Curseur != wxPoint( 0, 0 ) );
|
|
VECTOR2I cursorPos = m_controls->GetMousePosition();
|
|
|
|
if( aStartingPoint )
|
|
{
|
|
// Init the new item attributes
|
|
aGraphic->SetShape( (STROKE_T) aShape );
|
|
aGraphic->SetWidth( m_lineWidth );
|
|
aGraphic->SetLayer( getDrawingLayer() );
|
|
aGraphic->SetStart( wxPoint( aStartingPoint->x, aStartingPoint->y ) );
|
|
|
|
cursorPos = grid.BestSnapAnchor( cursorPos, aGraphic );
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
aGraphic->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
|
|
|
|
preview.Add( aGraphic );
|
|
m_controls->SetAutoPan( true );
|
|
m_controls->CaptureCursor( true );
|
|
|
|
if( !IsOCurseurSet )
|
|
m_frame->GetScreen()->m_O_Curseur = wxPoint( aStartingPoint->x, aStartingPoint->y );
|
|
|
|
started = true;
|
|
}
|
|
|
|
// Main loop: keep receiving events
|
|
while( OPT_TOOL_EVENT evt = Wait() )
|
|
{
|
|
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
|
|
grid.SetUseGrid( !evt->Modifier( MD_ALT ) );
|
|
m_controls->SetSnapping( !evt->Modifier( MD_ALT ) );
|
|
cursorPos = grid.BestSnapAnchor( m_controls->GetMousePosition(), getDrawingLayer() );
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
|
|
// 45 degree angle constraint enabled with an option and toggled with Ctrl
|
|
const bool limit45 = ( frame()->Settings().g_Use45DegreeGraphicSegments != !!( evt->Modifier( MD_CTRL ) ) );
|
|
|
|
if( direction45 != limit45 && started && aShape == S_SEGMENT )
|
|
{
|
|
direction45 = limit45;
|
|
|
|
if( direction45 )
|
|
{
|
|
const VECTOR2I lineVector( cursorPos - VECTOR2I( aGraphic->GetStart() ) );
|
|
|
|
// get a restricted 45/H/V line from the last fixed point to the cursor
|
|
auto newEnd = GetVectorSnapped45( lineVector );
|
|
aGraphic->SetEnd( aGraphic->GetStart() + (wxPoint) newEnd );
|
|
m_controls->ForceCursorPosition( true, VECTOR2I( aGraphic->GetEnd() ) );
|
|
}
|
|
else
|
|
{
|
|
aGraphic->SetEnd( (wxPoint) cursorPos );
|
|
}
|
|
|
|
m_view->Update( &preview );
|
|
frame()->SetMsgPanel( aGraphic );
|
|
}
|
|
|
|
if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
|
|
{
|
|
preview.Clear();
|
|
m_view->Update( &preview );
|
|
delete aGraphic;
|
|
aGraphic = NULL;
|
|
if( !IsOCurseurSet )
|
|
m_frame->GetScreen()->m_O_Curseur = wxPoint( 0, 0 );
|
|
break;
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::layerChanged ) )
|
|
{
|
|
m_lineWidth = getSegmentWidth( getDrawingLayer() );
|
|
aGraphic->SetLayer( getDrawingLayer() );
|
|
aGraphic->SetWidth( m_lineWidth );
|
|
m_view->Update( &preview );
|
|
frame()->SetMsgPanel( aGraphic );
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
m_menu.ShowContextMenu( selection() );
|
|
}
|
|
else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) )
|
|
{
|
|
if( !started )
|
|
{
|
|
m_lineWidth = getSegmentWidth( getDrawingLayer() );
|
|
|
|
// Init the new item attributes
|
|
aGraphic->SetShape( (STROKE_T) aShape );
|
|
aGraphic->SetWidth( m_lineWidth );
|
|
aGraphic->SetStart( (wxPoint) cursorPos );
|
|
aGraphic->SetEnd( (wxPoint) cursorPos );
|
|
aGraphic->SetLayer( getDrawingLayer() );
|
|
|
|
if( !IsOCurseurSet )
|
|
m_frame->GetScreen()->m_O_Curseur = (wxPoint) cursorPos;
|
|
|
|
preview.Add( aGraphic );
|
|
frame()->SetMsgPanel( aGraphic );
|
|
m_controls->SetAutoPan( true );
|
|
m_controls->CaptureCursor( true );
|
|
|
|
started = true;
|
|
}
|
|
else
|
|
{
|
|
auto snapItem = dyn_cast<DRAWSEGMENT*>( grid.GetSnapped() );
|
|
auto mod = dyn_cast<MODULE*>( m_frame->GetModel() );
|
|
|
|
if( aGraphic->GetEnd() == aGraphic->GetStart()
|
|
|| ( evt->IsDblClick( BUT_LEFT ) && aShape == S_SEGMENT )
|
|
|| snapItem )
|
|
// User has clicked twice in the same spot
|
|
// or clicked on the end of an existing segment (closing a path)
|
|
{
|
|
BOARD_COMMIT commit( m_frame );
|
|
|
|
// If the user clicks on an existing snap point from a drawsegment
|
|
// we finish the segment as they are likely closing a path
|
|
if( snapItem && aGraphic->GetLength() > 0.0 )
|
|
{
|
|
DRAWSEGMENT* l = m_editModules ? new EDGE_MODULE( mod ) : new DRAWSEGMENT;
|
|
|
|
*l = *aGraphic;
|
|
commit.Add( l );
|
|
}
|
|
|
|
if( !commit.Empty() )
|
|
commit.Push( _( "Draw a line" ) );
|
|
|
|
delete aGraphic;
|
|
aGraphic = NULL;
|
|
}
|
|
|
|
preview.Clear();
|
|
break;
|
|
}
|
|
}
|
|
else if( evt->IsMotion() )
|
|
{
|
|
// 45 degree lines
|
|
if( direction45 && aShape == S_SEGMENT )
|
|
{
|
|
const VECTOR2I lineVector( cursorPos - VECTOR2I( aGraphic->GetStart() ) );
|
|
|
|
// get a restricted 45/H/V line from the last fixed point to the cursor
|
|
auto newEnd = GetVectorSnapped45( lineVector );
|
|
aGraphic->SetEnd( aGraphic->GetStart() + (wxPoint) newEnd );
|
|
m_controls->ForceCursorPosition( true, VECTOR2I( aGraphic->GetEnd() ) );
|
|
}
|
|
else
|
|
aGraphic->SetEnd( (wxPoint) cursorPos );
|
|
|
|
m_view->Update( &preview );
|
|
|
|
if( started )
|
|
frame()->SetMsgPanel( aGraphic );
|
|
else
|
|
frame()->SetMsgPanel( board() );
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::incWidth ) )
|
|
{
|
|
m_lineWidth += WIDTH_STEP;
|
|
aGraphic->SetWidth( m_lineWidth );
|
|
m_view->Update( &preview );
|
|
frame()->SetMsgPanel( aGraphic );
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::decWidth ) && ( m_lineWidth > WIDTH_STEP ) )
|
|
{
|
|
m_lineWidth -= WIDTH_STEP;
|
|
aGraphic->SetWidth( m_lineWidth );
|
|
m_view->Update( &preview );
|
|
frame()->SetMsgPanel( aGraphic );
|
|
}
|
|
else if( evt->IsAction( &ACTIONS::resetLocalCoords ) )
|
|
{
|
|
IsOCurseurSet = true;
|
|
}
|
|
}
|
|
|
|
if( !IsOCurseurSet ) // reset the relative coordinte if it was not set before
|
|
m_frame->GetScreen()->m_O_Curseur = wxPoint( 0, 0 );
|
|
|
|
m_view->Remove( &preview );
|
|
frame()->SetMsgPanel( board() );
|
|
m_controls->SetAutoPan( false );
|
|
m_controls->CaptureCursor( false );
|
|
m_controls->ForceCursorPosition( false );
|
|
|
|
return started;
|
|
}
|
|
|
|
|
|
/**
|
|
* Update an arc DRAWSEGMENT from the current state
|
|
* of an Arc Geometry Manager
|
|
*/
|
|
static void updateArcFromConstructionMgr( const KIGFX::PREVIEW::ARC_GEOM_MANAGER& aMgr,
|
|
DRAWSEGMENT& aArc )
|
|
{
|
|
auto vec = aMgr.GetOrigin();
|
|
|
|
aArc.SetCenter( { vec.x, vec.y } );
|
|
|
|
vec = aMgr.GetStartRadiusEnd();
|
|
aArc.SetArcStart( { vec.x, vec.y } );
|
|
|
|
aArc.SetAngle( RAD2DECIDEG( -aMgr.GetSubtended() ) );
|
|
}
|
|
|
|
|
|
bool DRAWING_TOOL::drawArc( DRAWSEGMENT*& aGraphic )
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
|
|
|
|
m_lineWidth = getSegmentWidth( getDrawingLayer() );
|
|
|
|
// Arc geometric construction manager
|
|
KIGFX::PREVIEW::ARC_GEOM_MANAGER arcManager;
|
|
|
|
// Arc drawing assistant overlay
|
|
KIGFX::PREVIEW::ARC_ASSISTANT arcAsst( arcManager, m_frame->GetUserUnits() );
|
|
|
|
// Add a VIEW_GROUP that serves as a preview for the new item
|
|
PCBNEW_SELECTION preview;
|
|
m_view->Add( &preview );
|
|
m_view->Add( &arcAsst );
|
|
GRID_HELPER grid( m_frame );
|
|
|
|
m_controls->ShowCursor( true );
|
|
m_controls->SetSnapping( true );
|
|
|
|
Activate();
|
|
|
|
bool firstPoint = false;
|
|
|
|
// Main loop: keep receiving events
|
|
while( OPT_TOOL_EVENT evt = Wait() )
|
|
{
|
|
PCB_LAYER_ID layer = getDrawingLayer();
|
|
aGraphic->SetLayer( layer );
|
|
|
|
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
|
|
grid.SetUseGrid( !evt->Modifier( MD_ALT ) );
|
|
m_controls->SetSnapping( !evt->Modifier( MD_ALT ) );
|
|
VECTOR2I cursorPos = grid.BestSnapAnchor( m_controls->GetMousePosition(), aGraphic );
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
|
|
if( evt->IsClick( BUT_LEFT ) )
|
|
{
|
|
if( !firstPoint )
|
|
{
|
|
m_controls->SetAutoPan( true );
|
|
m_controls->CaptureCursor( true );
|
|
|
|
m_lineWidth = getSegmentWidth( getDrawingLayer() );
|
|
|
|
// Init the new item attributes
|
|
// (non-geometric, those are handled by the manager)
|
|
aGraphic->SetShape( S_ARC );
|
|
aGraphic->SetWidth( m_lineWidth );
|
|
|
|
preview.Add( aGraphic );
|
|
firstPoint = true;
|
|
}
|
|
|
|
arcManager.AddPoint( cursorPos, true );
|
|
}
|
|
else if( evt->IsAction( &deleteLastPoint ) )
|
|
{
|
|
arcManager.RemoveLastPoint();
|
|
}
|
|
else if( evt->IsMotion() )
|
|
{
|
|
// set angle snap
|
|
arcManager.SetAngleSnap( evt->Modifier( MD_CTRL ) );
|
|
|
|
// update, but don't step the manager state
|
|
arcManager.AddPoint( cursorPos, false );
|
|
}
|
|
else if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
|
|
{
|
|
preview.Clear();
|
|
delete aGraphic;
|
|
aGraphic = nullptr;
|
|
break;
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::layerChanged ) )
|
|
{
|
|
m_lineWidth = getSegmentWidth( getDrawingLayer() );
|
|
aGraphic->SetLayer( getDrawingLayer() );
|
|
aGraphic->SetWidth( m_lineWidth );
|
|
m_view->Update( &preview );
|
|
frame()->SetMsgPanel( aGraphic );
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
m_menu.ShowContextMenu( selection() );
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::incWidth ) )
|
|
{
|
|
m_lineWidth += WIDTH_STEP;
|
|
aGraphic->SetWidth( m_lineWidth );
|
|
m_view->Update( &preview );
|
|
frame()->SetMsgPanel( aGraphic );
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::decWidth ) && m_lineWidth > WIDTH_STEP )
|
|
{
|
|
m_lineWidth -= WIDTH_STEP;
|
|
aGraphic->SetWidth( m_lineWidth );
|
|
m_view->Update( &preview );
|
|
frame()->SetMsgPanel( aGraphic );
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::arcPosture ) )
|
|
{
|
|
arcManager.ToggleClockwise();
|
|
}
|
|
|
|
if( arcManager.IsComplete() )
|
|
{
|
|
break;
|
|
}
|
|
else if( arcManager.HasGeometryChanged() )
|
|
{
|
|
updateArcFromConstructionMgr( arcManager, *aGraphic );
|
|
m_view->Update( &preview );
|
|
m_view->Update( &arcAsst );
|
|
|
|
if(firstPoint)
|
|
frame()->SetMsgPanel( aGraphic );
|
|
else
|
|
frame()->SetMsgPanel( board() );
|
|
}
|
|
}
|
|
|
|
preview.Remove( aGraphic );
|
|
m_view->Remove( &arcAsst );
|
|
m_view->Remove( &preview );
|
|
frame()->SetMsgPanel( board() );
|
|
m_controls->SetAutoPan( false );
|
|
m_controls->CaptureCursor( false );
|
|
m_controls->ForceCursorPosition( false );
|
|
|
|
return !arcManager.IsReset();
|
|
}
|
|
|
|
|
|
bool DRAWING_TOOL::getSourceZoneForAction( ZONE_MODE aMode, ZONE_CONTAINER*& 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;
|
|
|
|
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
|
|
const PCBNEW_SELECTION& selection = selTool->GetSelection();
|
|
|
|
if( selection.Empty() )
|
|
{
|
|
clearSelection = true;
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
|
|
}
|
|
|
|
// we want a single zone
|
|
if( selection.Size() == 1 )
|
|
aZone = dyn_cast<ZONE_CONTAINER*>( selection[0] );
|
|
|
|
// expected a zone, but didn't get one
|
|
if( !aZone )
|
|
{
|
|
if( clearSelection )
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int DRAWING_TOOL::drawZone( bool aKeepout, ZONE_MODE aMode )
|
|
{
|
|
// 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_CONTAINER* sourceZone = nullptr;
|
|
|
|
if( !getSourceZoneForAction( aMode, sourceZone ) )
|
|
{
|
|
m_frame->SetNoToolSelected();
|
|
return 0;
|
|
}
|
|
|
|
ZONE_CREATE_HELPER::PARAMS params;
|
|
|
|
params.m_keepout = aKeepout;
|
|
params.m_mode = aMode;
|
|
params.m_sourceZone = sourceZone;
|
|
|
|
if( aMode == ZONE_MODE::GRAPHIC_POLYGON )
|
|
params.m_layer = getDrawingLayer();
|
|
else if( aMode == ZONE_MODE::SIMILAR )
|
|
params.m_layer = sourceZone->GetLayer();
|
|
else
|
|
params.m_layer = m_frame->GetActiveLayer();
|
|
|
|
ZONE_CREATE_HELPER zoneTool( *this, params );
|
|
|
|
// the geometry manager which handles the zone geometry, and
|
|
// hands the calculated points over to the zone creator tool
|
|
POLYGON_GEOM_MANAGER polyGeomMgr( zoneTool );
|
|
|
|
Activate(); // register for events
|
|
|
|
m_controls->ShowCursor( true );
|
|
m_controls->SetSnapping( true );
|
|
|
|
bool started = false;
|
|
GRID_HELPER grid( m_frame );
|
|
STATUS_TEXT_POPUP status( m_frame );
|
|
status.SetTextColor( wxColour( 255, 0, 0 ) );
|
|
status.SetText( _( "Self-intersecting polygons are not allowed" ) );
|
|
|
|
while( OPT_TOOL_EVENT evt = Wait() )
|
|
{
|
|
// This can be reset by some actions (e.g. Save Board), so ensure it stays set.
|
|
m_frame->GetGalCanvas()->SetCurrentCursor( wxCURSOR_PENCIL );
|
|
LSET layers( m_frame->GetActiveLayer() );
|
|
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
|
|
grid.SetUseGrid( !evt->Modifier( MD_ALT ) );
|
|
m_controls->SetSnapping( !evt->Modifier( MD_ALT ) );
|
|
VECTOR2I cursorPos = grid.BestSnapAnchor( m_controls->GetMousePosition(), layers );
|
|
m_controls->ForceCursorPosition( true, cursorPos );
|
|
|
|
if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
|
|
{
|
|
// pre-empted by another tool, give up
|
|
// cancelled without an inprogress polygon, give up
|
|
if( !polyGeomMgr.IsPolygonInProgress() || evt->IsActivate() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
polyGeomMgr.Reset();
|
|
// start again
|
|
started = false;
|
|
|
|
m_controls->SetAutoPan( false );
|
|
m_controls->CaptureCursor( false );
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::layerChanged ) )
|
|
{
|
|
if( aMode == ZONE_MODE::GRAPHIC_POLYGON )
|
|
params.m_layer = getDrawingLayer();
|
|
else if( aMode == ZONE_MODE::ADD || aMode == ZONE_MODE::CUTOUT )
|
|
params.m_layer = frame()->GetActiveLayer();
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
m_menu.ShowContextMenu( selection() );
|
|
}
|
|
// events that lock in nodes
|
|
else if( evt->IsClick( BUT_LEFT )
|
|
|| evt->IsDblClick( BUT_LEFT )
|
|
|| evt->IsAction( &closeZoneOutline ) )
|
|
{
|
|
// Check if it is double click / closing line (so we have to finish the zone)
|
|
const bool endPolygon = evt->IsDblClick( BUT_LEFT )
|
|
|| evt->IsAction( &closeZoneOutline )
|
|
|| 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 );
|
|
}
|
|
}
|
|
|
|
}
|
|
else if( evt->IsAction( &deleteLastPoint ) )
|
|
{
|
|
polyGeomMgr.DeleteLastCorner();
|
|
|
|
if( !polyGeomMgr.IsPolygonInProgress() )
|
|
{
|
|
// report finished as an empty shape
|
|
polyGeomMgr.SetFinished();
|
|
|
|
// start again
|
|
started = false;
|
|
m_controls->SetAutoPan( false );
|
|
m_controls->CaptureCursor( false );
|
|
}
|
|
}
|
|
else if( polyGeomMgr.IsPolygonInProgress()
|
|
&& ( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) )
|
|
{
|
|
polyGeomMgr.SetCursorPosition( cursorPos, evt->Modifier( MD_CTRL )
|
|
? POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45
|
|
: POLYGON_GEOM_MANAGER::LEADER_MODE::DIRECT );
|
|
|
|
if( polyGeomMgr.IsSelfIntersecting( true ) )
|
|
{
|
|
wxPoint p = wxGetMousePosition() + wxPoint( 20, 20 );
|
|
status.Move( p );
|
|
status.PopupFor( 1500 );
|
|
}
|
|
else
|
|
{
|
|
status.Hide();
|
|
}
|
|
}
|
|
} // end while
|
|
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
|
|
m_frame->SetNoToolSelected();
|
|
m_controls->ForceCursorPosition( false );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::DrawVia( const TOOL_EVENT& aEvent )
|
|
{
|
|
struct VIA_PLACER : public INTERACTIVE_PLACER_BASE
|
|
{
|
|
GRID_HELPER m_gridHelper;
|
|
|
|
VIA_PLACER( PCB_BASE_EDIT_FRAME* aFrame ) : m_gridHelper( aFrame )
|
|
{}
|
|
|
|
TRACK* findTrack( VIA* aVia )
|
|
{
|
|
const LSET lset = aVia->GetLayerSet();
|
|
std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> items;
|
|
BOX2I bbox = aVia->GetBoundingBox();
|
|
auto view = m_frame->GetGalCanvas()->GetView();
|
|
std::vector<TRACK*> possible_tracks;
|
|
|
|
view->Query( bbox, items );
|
|
|
|
for( auto it : items )
|
|
{
|
|
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( it.first );
|
|
|
|
if( !(item->GetLayerSet() & lset ).any() )
|
|
continue;
|
|
|
|
if( auto track = dyn_cast<TRACK*>( item ) )
|
|
{
|
|
if( TestSegmentHit( aVia->GetPosition(), track->GetStart(), track->GetEnd(),
|
|
( track->GetWidth() + aVia->GetWidth() ) / 2 ) )
|
|
possible_tracks.push_back( track );
|
|
}
|
|
}
|
|
|
|
TRACK* return_track = nullptr;
|
|
int min_d = std::numeric_limits<int>::max();
|
|
for( auto track : possible_tracks )
|
|
{
|
|
SEG test( track->GetStart(), track->GetEnd() );
|
|
auto dist = ( test.NearestPoint( aVia->GetPosition() ) -
|
|
VECTOR2I( aVia->GetPosition() ) ).EuclideanNorm();
|
|
|
|
if( dist < min_d )
|
|
{
|
|
min_d = dist;
|
|
return_track = track;
|
|
}
|
|
}
|
|
|
|
return return_track;
|
|
}
|
|
|
|
|
|
bool hasDRCViolation( VIA* aVia )
|
|
{
|
|
const LSET lset = aVia->GetLayerSet();
|
|
std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> items;
|
|
int net = 0;
|
|
int clearance = 0;
|
|
BOX2I bbox = aVia->GetBoundingBox();
|
|
auto view = m_frame->GetGalCanvas()->GetView();
|
|
|
|
view->Query( bbox, items );
|
|
|
|
for( auto it : items )
|
|
{
|
|
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( it.first );
|
|
|
|
if( !(item->GetLayerSet() & lset ).any() )
|
|
continue;
|
|
|
|
if( auto track = dyn_cast<TRACK*>( item ) )
|
|
{
|
|
int max_clearance = std::max( clearance, track->GetClearance() );
|
|
|
|
if( TestSegmentHit( aVia->GetPosition(), track->GetStart(), track->GetEnd(),
|
|
( track->GetWidth() + aVia->GetWidth() ) / 2 + max_clearance ) )
|
|
{
|
|
if( net && track->GetNetCode() != net )
|
|
return true;
|
|
|
|
net = track->GetNetCode();
|
|
clearance = track->GetClearance();
|
|
}
|
|
}
|
|
|
|
if( auto mod = dyn_cast<MODULE*>( item ) )
|
|
{
|
|
for( auto pad : mod->Pads() )
|
|
{
|
|
int max_clearance = std::max( clearance, pad->GetClearance() );
|
|
|
|
if( pad->HitTest( aVia->GetBoundingBox(), false, max_clearance ) )
|
|
{
|
|
if( net && pad->GetNetCode() != net )
|
|
return true;
|
|
|
|
net = pad->GetNetCode();
|
|
clearance = pad->GetClearance();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
int findStitchedZoneNet( VIA* aVia )
|
|
{
|
|
const auto pos = aVia->GetPosition();
|
|
const auto lset = aVia->GetLayerSet();
|
|
|
|
for( auto mod : m_board->Modules() )
|
|
{
|
|
for( D_PAD* pad : mod->Pads() )
|
|
{
|
|
if( pad->HitTest( pos ) && ( pad->GetLayerSet() & lset ).any() )
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
std::vector<ZONE_CONTAINER*> foundZones;
|
|
|
|
for( auto zone : m_board->Zones() )
|
|
{
|
|
if( zone->HitTestFilledArea( pos ) )
|
|
{
|
|
foundZones.push_back( zone );
|
|
}
|
|
}
|
|
|
|
std::sort( foundZones.begin(), foundZones.end(),
|
|
[] ( const ZONE_CONTAINER* a, const ZONE_CONTAINER* b ) {
|
|
return a->GetLayer() < b->GetLayer();
|
|
} );
|
|
|
|
// first take the net of the active layer
|
|
for( auto z : foundZones )
|
|
{
|
|
if( m_frame->GetActiveLayer() == z->GetLayer() )
|
|
return z->GetNetCode();
|
|
}
|
|
|
|
// none? take the topmost visible layer
|
|
for( auto z : foundZones )
|
|
{
|
|
if( m_board->IsLayerVisible( z->GetLayer() ) )
|
|
return z->GetNetCode();
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void SnapItem( BOARD_ITEM *aItem ) override
|
|
{
|
|
// If you place a Via on a track but not on its centerline, the current
|
|
// connectivity algorithm will require us to put a kink in the track when
|
|
// we break it (so that each of the two segments ends on the via center).
|
|
// That's not ideal, and is in fact probably worse than forcing snap in
|
|
// this situation.
|
|
|
|
m_gridHelper.SetSnap( !( m_modifiers & MD_SHIFT ) );
|
|
auto via = static_cast<VIA*>( aItem );
|
|
wxPoint pos = via->GetPosition();
|
|
TRACK* track = findTrack( via );
|
|
|
|
if( track )
|
|
{
|
|
SEG trackSeg( track->GetStart(), track->GetEnd() );
|
|
VECTOR2I snap = m_gridHelper.AlignToSegment( pos, trackSeg );
|
|
|
|
aItem->SetPosition( (wxPoint) snap );
|
|
}
|
|
}
|
|
|
|
bool PlaceItem( BOARD_ITEM* aItem, BOARD_COMMIT& aCommit ) override
|
|
{
|
|
auto via = static_cast<VIA*>( aItem );
|
|
int newNet;
|
|
TRACK* track = findTrack( via );
|
|
|
|
if( hasDRCViolation( via ) )
|
|
return false;
|
|
|
|
if( track )
|
|
{
|
|
aCommit.Modify( track );
|
|
TRACK* newTrack = dynamic_cast<TRACK*>( track->Clone() );
|
|
track->SetEnd( via->GetPosition() );
|
|
newTrack->SetStart( via->GetPosition() );
|
|
aCommit.Add( newTrack );
|
|
|
|
newNet = track->GetNetCode();
|
|
}
|
|
else
|
|
newNet = findStitchedZoneNet( via );
|
|
|
|
if( newNet > 0 )
|
|
via->SetNetCode( newNet );
|
|
|
|
aCommit.Add( aItem );
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<BOARD_ITEM> CreateItem() override
|
|
{
|
|
auto& ds = m_board->GetDesignSettings();
|
|
VIA* via = new VIA( m_board );
|
|
|
|
via->SetNetCode( 0 );
|
|
via->SetViaType( ds.m_CurrentViaType );
|
|
|
|
// for microvias, the size and hole will be changed later.
|
|
via->SetWidth( ds.GetCurrentViaSize() );
|
|
via->SetDrill( ds.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 VIA_BLIND_BURIED:
|
|
via->SetLayerPair( first_layer, last_layer );
|
|
break;
|
|
|
|
case VIA_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
|
|
NETINFO_ITEM* net = via->GetNet();
|
|
|
|
if( net )
|
|
{
|
|
via->SetWidth( net->GetMicroViaSize() );
|
|
via->SetDrill( net->GetMicroViaDrillSize() );
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return std::unique_ptr<BOARD_ITEM>( via );
|
|
}
|
|
};
|
|
|
|
VIA_PLACER placer( frame() );
|
|
|
|
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::VIA );
|
|
|
|
frame()->SetToolID( ID_PCB_DRAW_VIA_BUTT, wxCURSOR_PENCIL, _( "Add vias" ) );
|
|
|
|
doInteractiveItemPlacement( &placer, _( "Place via" ),
|
|
IPO_REPEAT | IPO_SINGLE_CLICK | IPO_ROTATE | IPO_FLIP );
|
|
|
|
frame()->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void DRAWING_TOOL::setTransitions()
|
|
{
|
|
Go( &DRAWING_TOOL::DrawLine, PCB_ACTIONS::drawLine.MakeEvent() );
|
|
Go( &DRAWING_TOOL::DrawGraphicPolygon, PCB_ACTIONS::drawPolygon.MakeEvent() );
|
|
Go( &DRAWING_TOOL::DrawCircle, PCB_ACTIONS::drawCircle.MakeEvent() );
|
|
Go( &DRAWING_TOOL::DrawArc, PCB_ACTIONS::drawArc.MakeEvent() );
|
|
Go( &DRAWING_TOOL::DrawDimension, PCB_ACTIONS::drawDimension.MakeEvent() );
|
|
Go( &DRAWING_TOOL::DrawZone, PCB_ACTIONS::drawZone.MakeEvent() );
|
|
Go( &DRAWING_TOOL::DrawZoneKeepout, PCB_ACTIONS::drawZoneKeepout.MakeEvent() );
|
|
Go( &DRAWING_TOOL::DrawZoneCutout, PCB_ACTIONS::drawZoneCutout.MakeEvent() );
|
|
Go( &DRAWING_TOOL::DrawSimilarZone, PCB_ACTIONS::drawSimilarZone.MakeEvent() );
|
|
Go( &DRAWING_TOOL::DrawVia, PCB_ACTIONS::drawVia.MakeEvent() );
|
|
Go( &DRAWING_TOOL::PlaceText, PCB_ACTIONS::placeText.MakeEvent() );
|
|
Go( &DRAWING_TOOL::PlaceImportedGraphics, PCB_ACTIONS::placeImportedGraphics.MakeEvent() );
|
|
Go( &DRAWING_TOOL::SetAnchor, PCB_ACTIONS::setAnchor.MakeEvent() );
|
|
}
|
|
|
|
|
|
int DRAWING_TOOL::getSegmentWidth( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
assert( m_board );
|
|
return m_board->GetDesignSettings().GetLineThickness( aLayer );
|
|
}
|
|
|
|
|
|
PCB_LAYER_ID DRAWING_TOOL::getDrawingLayer() const
|
|
{
|
|
PCB_LAYER_ID layer = m_frame->GetActiveLayer();
|
|
|
|
if( ( GetDrawingMode() == MODE::DIMENSION || GetDrawingMode() == MODE::GRAPHIC_POLYGON )
|
|
&& IsCopperLayer( layer ) )
|
|
{
|
|
if( layer == F_Cu )
|
|
layer = F_SilkS;
|
|
else if( layer == B_Cu )
|
|
layer = B_SilkS;
|
|
else
|
|
layer = Dwgs_User;
|
|
|
|
m_frame->SetActiveLayer( layer );
|
|
}
|
|
|
|
return layer;
|
|
}
|
|
|
|
|
|
const unsigned int DRAWING_TOOL::WIDTH_STEP = Millimeter2iu( 0.1 );
|