kicad/pcbnew/tools/drawing_tool.cpp

1324 lines
42 KiB
C++
Raw Normal View History

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2014 CERN
* @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 "common_actions.h"
#include <wxPcbStruct.h>
#include <class_draw_panel_gal.h>
2014-05-12 15:17:51 +00:00
#include <project.h>
#include <id.h>
#include <pcbnew_id.h>
2014-02-13 11:46:39 +00:00
#include <confirm.h>
#include <dialog_edit_module_text.h>
2014-07-09 13:02:56 +00:00
#include <import_dxf/dialog_dxf_import.h>
2014-02-13 11:46:39 +00:00
#include <view/view_group.h>
#include <view/view_controls.h>
2014-02-13 11:46:39 +00:00
#include <gal/graphics_abstraction_layer.h>
#include <tool/tool_manager.h>
#include <router/direction.h>
#include <ratsnest_data.h>
2016-06-21 15:06:28 +00:00
#include <board_commit.h>
2014-02-13 11:46:39 +00:00
#include <class_board.h>
#include <class_edge_mod.h>
2014-02-10 14:40:25 +00:00
#include <class_pcb_text.h>
2014-02-11 13:38:44 +00:00
#include <class_dimension.h>
2014-02-13 15:10:32 +00:00
#include <class_zone.h>
2014-02-13 11:46:39 +00:00
#include <class_module.h>
DRAWING_TOOL::DRAWING_TOOL() :
2016-05-10 16:09:39 +00:00
PCB_TOOL( "pcbnew.InteractiveDrawing" ), m_view( NULL ),
m_controls( NULL ), m_board( NULL ), m_frame( NULL ), m_lineWidth( 1 )
{
}
DRAWING_TOOL::~DRAWING_TOOL()
{
}
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>();
}
int DRAWING_TOOL::DrawLine( const TOOL_EVENT& aEvent )
{
BOARD_ITEM_CONTAINER* parent = m_frame->GetModel();
DRAWSEGMENT* line = m_editModules ? new EDGE_MODULE( (MODULE*) parent ) : new DRAWSEGMENT;
boost::optional<VECTOR2D> startingPoint;
2016-06-21 15:06:28 +00:00
BOARD_COMMIT commit( m_frame );
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 ) )
{
if( line )
{
2016-06-21 15:06:28 +00:00
commit.Add( line );
commit.Push( _( "Draw a line segment" ) );
startingPoint = line->GetEnd();
}
else
{
startingPoint = boost::none;
}
line = m_editModules ? new EDGE_MODULE( (MODULE*) parent ) : new DRAWSEGMENT;
}
m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString );
return 0;
}
int DRAWING_TOOL::DrawCircle( const TOOL_EVENT& aEvent )
2014-02-10 09:58:58 +00:00
{
BOARD_ITEM_CONTAINER* parent = m_frame->GetModel();
DRAWSEGMENT* circle = m_editModules ? new EDGE_MODULE( (MODULE*) parent ) : new DRAWSEGMENT;
2016-06-21 15:06:28 +00:00
BOARD_COMMIT commit( m_frame );
2014-02-10 09:58:58 +00:00
m_frame->SetToolID( m_editModules ? ID_MODEDIT_CIRCLE_TOOL : ID_PCB_CIRCLE_BUTT,
wxCURSOR_PENCIL, _( "Add graphic circle" ) );
2014-02-14 10:35:48 +00:00
while( drawSegment( S_CIRCLE, circle ) )
{
if( circle )
{
2016-06-21 15:06:28 +00:00
commit.Add( circle );
commit.Push( _( "Draw a circle" ) );
}
circle = m_editModules ? new EDGE_MODULE( (MODULE*) parent ) : new DRAWSEGMENT;
}
m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString );
return 0;
}
int DRAWING_TOOL::DrawArc( const TOOL_EVENT& aEvent )
{
BOARD_ITEM_CONTAINER* parent = m_frame->GetModel();
DRAWSEGMENT* arc = m_editModules ? new EDGE_MODULE( (MODULE*) parent ) : new DRAWSEGMENT;
2016-06-21 15:06:28 +00:00
BOARD_COMMIT commit( m_frame );
m_frame->SetToolID( m_editModules ? ID_MODEDIT_ARC_TOOL : ID_PCB_ARC_BUTT,
wxCURSOR_PENCIL, _( "Add graphic arc" ) );
while( drawArc( arc ) )
{
if( arc )
{
2016-06-21 15:06:28 +00:00
commit.Add( arc );
commit.Push( _( "Draw an arc" ) );
}
arc = m_editModules ? new EDGE_MODULE( (MODULE*) parent ) : new DRAWSEGMENT;
}
2014-02-10 09:58:58 +00:00
m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString );
2014-02-10 09:58:58 +00:00
return 0;
}
2014-02-10 09:58:58 +00:00
int DRAWING_TOOL::PlaceText( const TOOL_EVENT& aEvent )
{
BOARD_ITEM* text = NULL;
const BOARD_DESIGN_SETTINGS& dsnSettings = m_frame->GetDesignSettings();
2016-06-21 15:06:28 +00:00
BOARD_COMMIT commit( m_frame );
// Add a VIEW_GROUP that serves as a preview for the new item
KIGFX::VIEW_GROUP preview( m_view );
m_view->Add( &preview );
m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear, true );
m_controls->ShowCursor( true );
m_controls->SetSnapping( true );
// do not capture or auto-pan until we start placing some text
Activate();
m_frame->SetToolID( m_editModules ? ID_MODEDIT_TEXT_TOOL : ID_PCB_ADD_TEXT_BUTT,
wxCURSOR_PENCIL, _( "Add text" ) );
// Main loop: keep receiving events
while( OPT_TOOL_EVENT evt = Wait() )
{
VECTOR2I cursorPos = m_controls->GetCursorPosition();
2014-02-10 09:58:58 +00:00
if( evt->IsCancel() || evt->IsActivate() )
{
if( text )
{
// Delete the old text and have another try
2016-06-21 15:06:28 +00:00
delete text;
text = NULL;
preview.Clear();
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
m_controls->ShowCursor( true );
}
else
break;
2014-02-10 09:58:58 +00:00
if( evt->IsActivate() ) // now finish unconditionally
break;
}
else if( text && evt->Category() == TC_COMMAND )
{
if( evt->IsAction( &COMMON_ACTIONS::rotate ) )
{
text->Rotate( text->GetPosition(), m_frame->GetRotationAngle() );
preview.ViewUpdate();
}
// TODO rotate CCW
else if( evt->IsAction( &COMMON_ACTIONS::flip ) )
{
text->Flip( text->GetPosition() );
preview.ViewUpdate();
}
}
else if( evt->IsClick( BUT_LEFT ) )
{
if( !text )
{
// Init the new item attributes
if( m_editModules )
{
TEXTE_MODULE* textMod = new TEXTE_MODULE( (MODULE*) m_frame->GetModel() );
2016-06-21 15:06:28 +00:00
textMod->SetLayer( m_frame->GetActiveLayer() );
textMod->SetSize( dsnSettings.m_ModuleTextSize );
textMod->SetThickness( dsnSettings.m_ModuleTextWidth );
textMod->SetTextPosition( wxPoint( cursorPos.x, cursorPos.y ) );
DialogEditModuleText textDialog( m_frame, textMod, NULL );
bool placing;
RunMainStack( [&]() {
placing = textDialog.ShowModal() && ( textMod->GetText().Length() > 0 );
} );
if( placing )
text = textMod;
else
delete textMod;
}
else
{
2016-06-21 15:06:28 +00:00
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 );
LAYER_ID layer = m_frame->GetActiveLayer();
textPcb->SetLayer( layer );
// Set the mirrored option for layers on the BACK side of the board
if( IsBackLayer( layer ) )
textPcb->SetMirrored( true );
textPcb->SetSize( dsnSettings.m_PcbTextSize );
textPcb->SetThickness( dsnSettings.m_PcbTextWidth );
textPcb->SetTextPosition( wxPoint( cursorPos.x, cursorPos.y ) );
RunMainStack( [&]() {
getEditFrame<PCB_EDIT_FRAME>()->InstallTextPCBOptionsFrame( textPcb, NULL );
} );
2016-06-21 15:06:28 +00:00
if( textPcb->GetText().IsEmpty() )
delete textPcb;
else
text = textPcb;
}
if( text == NULL )
continue;
m_controls->CaptureCursor( true );
m_controls->SetAutoPan( true );
//m_controls->ShowCursor( false );
preview.Add( text );
}
else
{
//assert( text->GetText().Length() > 0 );
//assert( text->GetSize().x > 0 && text->GetSize().y > 0 );
text->ClearFlags();
2016-06-21 15:06:28 +00:00
preview.Remove( text );
2016-06-21 15:06:28 +00:00
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.x, cursorPos.y ) );
// Show a preview of the item
preview.ViewUpdate();
}
2014-02-10 09:58:58 +00:00
}
m_controls->ShowCursor( false );
m_controls->SetSnapping( false );
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
m_view->Remove( &preview );
m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString );
2014-02-10 09:58:58 +00:00
return 0;
2014-02-10 14:40:25 +00:00
}
int DRAWING_TOOL::DrawDimension( const TOOL_EVENT& aEvent )
2014-02-11 13:38:44 +00:00
{
2014-02-24 10:17:49 +00:00
DIMENSION* dimension = NULL;
2016-06-21 15:06:28 +00:00
BOARD_COMMIT commit( m_frame );
int maxThickness;
2014-02-11 13:38:44 +00:00
// Add a VIEW_GROUP that serves as a preview for the new item
KIGFX::VIEW_GROUP preview( m_view );
m_view->Add( &preview );
2014-02-11 13:38:44 +00:00
m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear, true );
m_controls->ShowCursor( true );
m_controls->SetSnapping( true );
2014-02-11 13:38:44 +00:00
Activate();
m_frame->SetToolID( ID_PCB_DIMENSION_BUTT, wxCURSOR_PENCIL, _( "Add dimension" ) );
2014-02-11 13:38:44 +00:00
2014-02-14 10:35:48 +00:00
enum DIMENSION_STEPS
{
SET_ORIGIN = 0,
SET_END,
SET_HEIGHT,
FINISHED
};
int step = SET_ORIGIN;
2014-02-11 13:38:44 +00:00
// Main loop: keep receiving events
while( OPT_TOOL_EVENT evt = Wait() )
{
VECTOR2I cursorPos = m_controls->GetCursorPosition();
2014-02-11 13:38:44 +00:00
if( evt->IsCancel() || evt->IsActivate() )
2014-02-11 13:38:44 +00:00
{
if( step != SET_ORIGIN ) // start from the beginning
{
preview.Clear();
delete dimension;
step = SET_ORIGIN;
}
else
break;
if( evt->IsActivate() ) // now finish unconditionally
break;
2014-02-11 13:38:44 +00:00
}
else if( evt->IsAction( &COMMON_ACTIONS::incWidth ) && step != SET_ORIGIN )
2014-02-11 13:38:44 +00:00
{
dimension->SetWidth( dimension->GetWidth() + WIDTH_STEP );
preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
}
2014-02-11 13:38:44 +00:00
else if( evt->IsAction( &COMMON_ACTIONS::decWidth ) && step != SET_ORIGIN )
{
int width = dimension->GetWidth();
2014-02-11 13:38:44 +00:00
if( width > WIDTH_STEP )
{
dimension->SetWidth( width - WIDTH_STEP );
preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
}
2014-02-11 13:38:44 +00:00
}
else if( evt->IsClick( BUT_LEFT ) )
{
switch( step )
{
2014-02-14 10:35:48 +00:00
case SET_ORIGIN:
2014-02-11 13:38:44 +00:00
{
LAYER_ID layer = m_frame->GetScreen()->m_Active_Layer;
if( IsCopperLayer( layer ) || layer == Edge_Cuts )
{
DisplayInfoMessage( NULL, _( "Dimension not allowed on Copper or Edge Cut layers" ) );
--step;
}
else
{
// Init the new item attributes
dimension = new DIMENSION( m_board );
dimension->SetLayer( layer );
dimension->SetOrigin( wxPoint( cursorPos.x, cursorPos.y ) );
dimension->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
dimension->Text().SetSize( m_board->GetDesignSettings().m_PcbTextSize );
int width = m_board->GetDesignSettings().m_PcbTextWidth;
maxThickness = Clamp_Text_PenSize( width, dimension->Text().GetSize() );
if( width > maxThickness )
width = maxThickness;
dimension->Text().SetThickness( width );
dimension->SetWidth( width );
dimension->AdjustDimensionDetails();
2014-02-11 13:38:44 +00:00
preview.Add( dimension );
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( true );
}
2014-02-11 13:38:44 +00:00
}
break;
2014-02-11 13:38:44 +00:00
case SET_END:
2014-02-19 14:20:42 +00:00
dimension->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
// Dimensions that have origin and end in the same spot are not valid
2014-02-19 14:20:42 +00:00
if( dimension->GetOrigin() == dimension->GetEnd() )
--step;
break;
2014-02-14 10:35:48 +00:00
case SET_HEIGHT:
2014-02-11 13:38:44 +00:00
{
if( wxPoint( cursorPos.x, cursorPos.y ) != dimension->GetPosition() )
{
assert( dimension->GetOrigin() != dimension->GetEnd() );
assert( dimension->GetWidth() > 0 );
2016-06-21 15:06:28 +00:00
preview.Remove( dimension );
2016-06-21 15:06:28 +00:00
commit.Add( dimension );
commit.Push( _( "Draw a dimension" ) );
}
2014-02-11 13:38:44 +00:00
}
break;
2014-02-11 13:38:44 +00:00
}
2014-02-14 10:35:48 +00:00
if( ++step == FINISHED )
{
step = SET_ORIGIN;
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
}
2014-02-11 13:38:44 +00:00
}
else if( evt->IsMotion() )
{
switch( step )
{
2014-02-14 10:35:48 +00:00
case SET_END:
2014-02-11 13:38:44 +00:00
dimension->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
break;
2014-02-14 10:35:48 +00:00
case SET_HEIGHT:
2014-02-11 13:38:44 +00:00
{
2014-02-14 10:35:48 +00:00
// Calculating the direction of travel perpendicular to the selected axis
2014-02-11 13:38:44 +00:00
double angle = dimension->GetAngle() + ( M_PI / 2 );
wxPoint pos( cursorPos.x, cursorPos.y );
wxPoint delta( pos - dimension->m_featureLineDO );
double height = ( delta.x * cos( angle ) ) + ( delta.y * sin( angle ) );
dimension->SetHeight( height );
}
break;
}
// Show a preview of the item
preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
}
}
if( step != SET_ORIGIN )
delete dimension;
m_controls->ShowCursor( false );
m_controls->SetSnapping( false );
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
m_view->Remove( &preview );
2014-02-11 13:38:44 +00:00
m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString );
2014-02-11 13:38:44 +00:00
return 0;
}
int DRAWING_TOOL::DrawZone( const TOOL_EVENT& aEvent )
2014-02-13 15:10:32 +00:00
{
m_frame->SetToolID( ID_PCB_ZONES_BUTT, wxCURSOR_PENCIL, _( "Add zones" ) );
return drawZone( false );
}
2014-02-13 15:10:32 +00:00
int DRAWING_TOOL::DrawKeepout( const TOOL_EVENT& aEvent )
{
m_frame->SetToolID( ID_PCB_KEEPOUT_AREA_BUTT, wxCURSOR_PENCIL, _( "Add keepout" ) );
return drawZone( true );
}
2014-02-13 15:10:32 +00:00
int DRAWING_TOOL::PlaceDXF( const TOOL_EVENT& aEvent )
2014-07-09 13:02:56 +00:00
{
if( !m_frame->GetModel() )
return 0;
2014-07-09 13:02:56 +00:00
DIALOG_DXF_IMPORT dlg( m_frame );
int dlgResult = dlg.ShowModal();
const std::list<BOARD_ITEM*>& list = dlg.GetImportedItems();
2015-07-30 11:49:36 +00:00
if( dlgResult != wxID_OK || list.empty() )
2014-07-09 13:02:56 +00:00
return 0;
VECTOR2I cursorPos = m_controls->GetCursorPosition();
VECTOR2I delta = cursorPos - (*list.begin())->GetPosition();
// Add a VIEW_GROUP that serves as a preview for the new item
KIGFX::VIEW_GROUP preview( m_view );
2016-06-21 15:06:28 +00:00
BOARD_COMMIT commit( m_frame );
2014-07-09 13:02:56 +00:00
// Build the undo list & add items to the current view
for( auto it = list.begin(), itEnd = list.end(); it != itEnd; ++it )
2014-07-09 13:02:56 +00:00
{
2015-08-03 19:11:59 +00:00
KICAD_T type = (*it)->Type();
assert( type == PCB_LINE_T || type == PCB_TEXT_T );
2014-07-09 13:02:56 +00:00
2015-08-03 19:11:59 +00:00
if( type == PCB_LINE_T || type == PCB_TEXT_T )
preview.Add( *it );
2014-07-09 13:02:56 +00:00
}
BOARD_ITEM* firstItem = static_cast<BOARD_ITEM*>( *preview.Begin() );
m_view->Add( &preview );
m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear, true );
2014-07-09 13:02:56 +00:00
m_controls->ShowCursor( true );
m_controls->SetSnapping( true );
Activate();
// Main loop: keep receiving events
while( OPT_TOOL_EVENT evt = Wait() )
{
cursorPos = m_controls->GetCursorPosition();
if( evt->IsMotion() )
{
delta = cursorPos - firstItem->GetPosition();
for( KIGFX::VIEW_GROUP::const_iter it = preview.Begin(), end = preview.End(); it != end; ++it )
2014-07-09 13:02:56 +00:00
static_cast<BOARD_ITEM*>( *it )->Move( wxPoint( delta.x, delta.y ) );
preview.ViewUpdate();
}
else if( evt->Category() == TC_COMMAND )
{
2016-06-21 15:06:28 +00:00
// TODO it should be handled by EDIT_TOOL, so add items and select?
2014-07-09 13:02:56 +00:00
if( evt->IsAction( &COMMON_ACTIONS::rotate ) )
{
for( KIGFX::VIEW_GROUP::const_iter it = preview.Begin(), end = preview.End(); it != end; ++it )
2014-07-09 13:02:56 +00:00
static_cast<BOARD_ITEM*>( *it )->Rotate( wxPoint( cursorPos.x, cursorPos.y ),
m_frame->GetRotationAngle() );
preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
}
else if( evt->IsAction( &COMMON_ACTIONS::flip ) )
{
for( KIGFX::VIEW_GROUP::const_iter it = preview.Begin(), end = preview.End(); it != end; ++it )
2014-07-09 13:02:56 +00:00
static_cast<BOARD_ITEM*>( *it )->Flip( wxPoint( cursorPos.x, cursorPos.y ) );
preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
}
else if( evt->IsCancel() || evt->IsActivate() )
{
preview.FreeItems();
break;
}
}
else if( evt->IsClick( BUT_LEFT ) )
{
// Place the drawing
PICKED_ITEMS_LIST picklist;
BOARD_ITEM_CONTAINER* parent = m_frame->GetModel();
for( KIGFX::VIEW_GROUP::const_iter it = preview.Begin(); it != preview.End(); ++it )
2014-07-09 13:02:56 +00:00
{
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( *it );
2014-07-09 13:02:56 +00:00
if( m_editModules )
2014-07-09 13:02:56 +00:00
{
2015-08-03 19:11:59 +00:00
// Modules use different types for the same things,
// so we need to convert imported items to appropriate classes.
BOARD_ITEM* converted = NULL;
2014-07-09 13:02:56 +00:00
switch( item->Type() )
{
2015-08-03 19:11:59 +00:00
case PCB_TEXT_T:
converted = new TEXTE_MODULE( (MODULE*) parent );
2015-08-03 19:11:59 +00:00
// Copy coordinates, layer, etc.
*static_cast<BOARD_ITEM*>( converted ) = *static_cast<BOARD_ITEM*>( item );
2014-07-09 13:02:56 +00:00
break;
2015-08-03 19:11:59 +00:00
case PCB_LINE_T:
converted = new EDGE_MODULE( (MODULE*) parent );
2015-08-03 19:11:59 +00:00
// Copy coordinates, layer, etc.
*static_cast<DRAWSEGMENT*>( converted ) = *static_cast<DRAWSEGMENT*>( item );
2014-07-09 13:02:56 +00:00
break;
default:
assert( false );
break;
}
2015-08-03 19:11:59 +00:00
delete item;
item = converted;
2014-07-09 13:02:56 +00:00
}
2016-06-21 15:06:28 +00:00
commit.Add( item );
2014-07-09 13:02:56 +00:00
}
2016-06-21 15:06:28 +00:00
commit.Push( _( "Place a DXF drawing" ) );
2014-07-09 13:02:56 +00:00
break;
}
}
preview.Clear();
m_controls->ShowCursor( false );
m_controls->SetSnapping( false );
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
2014-07-09 13:02:56 +00:00
m_view->Remove( &preview );
return 0;
}
int DRAWING_TOOL::SetAnchor( const TOOL_EVENT& aEvent )
{
assert( m_editModules );
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() )
{
if( evt->IsClick( BUT_LEFT ) )
{
MODULE* module = (MODULE*) m_frame->GetModel();
2016-06-21 15:06:28 +00:00
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.x, cursorPos.y );
module->MoveAnchorPosition( moveVector );
2016-06-21 15:06:28 +00:00
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->IsCancel() || evt->IsActivate() )
break;
}
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
m_controls->SetSnapping( false );
m_controls->ShowCursor( false );
m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString );
return 0;
}
bool DRAWING_TOOL::drawSegment( int aShape, DRAWSEGMENT*& aGraphic,
boost::optional<VECTOR2D> aStartingPoint )
2014-02-11 16:15:33 +00:00
{
// Only two shapes are currently supported
assert( aShape == S_SEGMENT || aShape == S_CIRCLE );
DRAWSEGMENT line45;
2014-02-11 16:15:33 +00:00
// Add a VIEW_GROUP that serves as a preview for the new item
KIGFX::VIEW_GROUP preview( m_view );
m_view->Add( &preview );
2014-02-11 16:15:33 +00:00
m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear, true );
m_controls->ShowCursor( true );
m_controls->SetSnapping( true );
2014-02-11 16:15:33 +00:00
Activate();
bool direction45 = false; // 45 degrees only mode
bool started = false;
VECTOR2I cursorPos = m_controls->GetCursorPosition();
if( aStartingPoint )
{
LAYER_ID layer = m_frame->GetScreen()->m_Active_Layer;
// Init the new item attributes
aGraphic->SetShape( (STROKE_T) aShape );
2015-02-15 22:21:52 +00:00
aGraphic->SetWidth( m_lineWidth );
aGraphic->SetStart( wxPoint( aStartingPoint->x, aStartingPoint->y ) );
aGraphic->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
aGraphic->SetLayer( layer );
if( aShape == S_SEGMENT )
line45 = *aGraphic; // used only for direction 45 mode with lines
preview.Add( aGraphic );
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( true );
started = true;
}
2014-02-11 16:15:33 +00:00
// Main loop: keep receiving events
while( OPT_TOOL_EVENT evt = Wait() )
{
bool updatePreview = false; // should preview be updated
cursorPos = m_controls->GetCursorPosition();
2014-02-11 16:15:33 +00:00
// Enable 45 degrees lines only mode by holding control
if( direction45 != evt->Modifier( MD_CTRL ) && started && aShape == S_SEGMENT )
{
direction45 = evt->Modifier( MD_CTRL );
if( direction45 )
{
preview.Add( &line45 );
make45DegLine( aGraphic, &line45 );
}
else
{
preview.Remove( &line45 );
aGraphic->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
}
updatePreview = true;
}
if( evt->IsCancel() || evt->IsActivate() || evt->IsAction( &COMMON_ACTIONS::layerChanged ) )
{
2014-02-19 14:20:42 +00:00
preview.Clear();
updatePreview = true;
delete aGraphic;
aGraphic = NULL;
break;
}
2014-02-11 16:15:33 +00:00
2015-07-03 18:58:13 +00:00
else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) )
2014-02-11 16:15:33 +00:00
{
if( !started )
{
LAYER_ID layer = m_frame->GetScreen()->m_Active_Layer;
if( IsCopperLayer( layer ) )
{
DisplayInfoMessage( NULL, _( "Graphic not allowed on Copper layers" ) );
}
else
{
// Init the new item attributes
aGraphic->SetShape( (STROKE_T) aShape );
2015-02-15 22:21:52 +00:00
m_lineWidth = getSegmentWidth( layer );
aGraphic->SetWidth( m_lineWidth );
aGraphic->SetStart( wxPoint( cursorPos.x, cursorPos.y ) );
aGraphic->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
aGraphic->SetLayer( layer );
if( aShape == S_SEGMENT )
line45 = *aGraphic; // used only for direction 45 mode with lines
preview.Add( aGraphic );
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( true );
started = true;
}
}
else
{
2015-07-03 18:58:13 +00:00
if( aGraphic->GetEnd() == aGraphic->GetStart() ||
( evt->IsDblClick( BUT_LEFT ) && aShape == S_SEGMENT ) )
// User has clicked twice in the same spot
{ // a clear sign that the current drawing is finished
// Now we have to add the helper line as well
2015-07-03 18:58:13 +00:00
if( direction45 )
{
BOARD_ITEM_CONTAINER* parent = m_frame->GetModel();
DRAWSEGMENT* l = m_editModules ? new EDGE_MODULE( (MODULE*) parent )
: new DRAWSEGMENT;
// Copy coordinates, layer, etc.
*static_cast<DRAWSEGMENT*>( l ) = line45;
l->SetEnd( aGraphic->GetStart() );
2016-06-21 15:06:28 +00:00
BOARD_COMMIT commit( m_frame );
commit.Add( l );
commit.Push( _( "Draw a line" ) );
2015-07-03 18:58:13 +00:00
}
delete aGraphic;
aGraphic = NULL;
2015-07-03 18:58:13 +00:00
}
preview.Clear();
break;
}
2014-02-11 16:15:33 +00:00
}
else if( evt->IsMotion() )
2014-02-11 16:15:33 +00:00
{
// 45 degree lines
if( direction45 && aShape == S_SEGMENT )
make45DegLine( aGraphic, &line45 );
else
aGraphic->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
updatePreview = true;
2014-02-11 16:15:33 +00:00
}
else if( evt->IsAction( &COMMON_ACTIONS::incWidth ) )
{
2015-02-15 22:21:52 +00:00
m_lineWidth += WIDTH_STEP;
aGraphic->SetWidth( m_lineWidth );
updatePreview = true;
}
else if( evt->IsAction( &COMMON_ACTIONS::decWidth ) )
{
2015-02-15 22:21:52 +00:00
if( m_lineWidth > (unsigned) WIDTH_STEP )
{
2015-02-15 22:21:52 +00:00
m_lineWidth -= WIDTH_STEP;
aGraphic->SetWidth( m_lineWidth );
updatePreview = true;
}
}
if( updatePreview )
preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
2014-02-11 16:15:33 +00:00
}
m_controls->ShowCursor( false );
m_controls->SetSnapping( false );
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
m_view->Remove( &preview );
return started;
}
2014-02-11 16:15:33 +00:00
bool DRAWING_TOOL::drawArc( DRAWSEGMENT*& aGraphic )
{
bool clockwise = true; // drawing direction of the arc
double startAngle = 0.0f; // angle of the first arc line
VECTOR2I cursorPos = m_controls->GetCursorPosition();
DRAWSEGMENT helperLine;
helperLine.SetShape( S_SEGMENT );
helperLine.SetLayer( Dwgs_User );
helperLine.SetWidth( 1 );
// Add a VIEW_GROUP that serves as a preview for the new item
KIGFX::VIEW_GROUP preview( m_view );
m_view->Add( &preview );
m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear, true );
m_controls->ShowCursor( true );
m_controls->SetSnapping( true );
Activate();
enum ARC_STEPS
{
SET_ORIGIN = 0,
SET_END,
SET_ANGLE,
FINISHED
};
int step = SET_ORIGIN;
// Main loop: keep receiving events
while( OPT_TOOL_EVENT evt = Wait() )
{
cursorPos = m_controls->GetCursorPosition();
if( evt->IsCancel() || evt->IsActivate() )
{
preview.Clear();
delete aGraphic;
aGraphic = NULL;
break;
}
else if( evt->IsClick( BUT_LEFT ) )
{
switch( step )
{
case SET_ORIGIN:
{
LAYER_ID layer = m_frame->GetScreen()->m_Active_Layer;
if( IsCopperLayer( layer ) )
{
DisplayInfoMessage( NULL, _( "Graphic not allowed on Copper layers" ) );
--step;
}
else
{
// Init the new item attributes
aGraphic->SetShape( S_ARC );
aGraphic->SetAngle( 0.0 );
aGraphic->SetWidth( getSegmentWidth( layer ) );
aGraphic->SetCenter( wxPoint( cursorPos.x, cursorPos.y ) );
aGraphic->SetLayer( layer );
helperLine.SetStart( aGraphic->GetCenter() );
helperLine.SetEnd( aGraphic->GetCenter() );
preview.Add( aGraphic );
preview.Add( &helperLine );
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( true );
}
}
break;
case SET_END:
{
if( wxPoint( cursorPos.x, cursorPos.y ) != aGraphic->GetCenter() )
{
VECTOR2D startLine( aGraphic->GetArcStart() - aGraphic->GetCenter() );
startAngle = startLine.Angle();
aGraphic->SetArcStart( wxPoint( cursorPos.x, cursorPos.y ) );
}
else
--step; // one another chance to draw a proper arc
}
break;
case SET_ANGLE:
{
if( wxPoint( cursorPos.x, cursorPos.y ) != aGraphic->GetArcStart() && aGraphic->GetAngle() != 0 )
{
assert( aGraphic->GetArcStart() != aGraphic->GetArcEnd() );
assert( aGraphic->GetWidth() > 0 );
m_view->Add( aGraphic );
aGraphic->ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
preview.Remove( aGraphic );
preview.Remove( &helperLine );
}
else
--step; // one another chance to draw a proper arc
}
break;
}
if( ++step == FINISHED )
break;
}
else if( evt->IsMotion() )
{
switch( step )
{
case SET_END:
helperLine.SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
aGraphic->SetArcStart( wxPoint( cursorPos.x, cursorPos.y ) );
break;
case SET_ANGLE:
{
VECTOR2D endLine( wxPoint( cursorPos.x, cursorPos.y ) - aGraphic->GetCenter() );
double newAngle = RAD2DECIDEG( endLine.Angle() - startAngle );
// Adjust the new angle to (counter)clockwise setting
if( clockwise && newAngle < 0.0 )
newAngle += 3600.0;
else if( !clockwise && newAngle > 0.0 )
newAngle -= 3600.0;
aGraphic->SetAngle( newAngle );
}
break;
}
// Show a preview of the item
preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
}
else if( evt->IsAction( &COMMON_ACTIONS::incWidth ) )
{
aGraphic->SetWidth( aGraphic->GetWidth() + WIDTH_STEP );
preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
}
else if( evt->IsAction( &COMMON_ACTIONS::decWidth ) )
{
int width = aGraphic->GetWidth();
if( width > WIDTH_STEP )
{
aGraphic->SetWidth( width - WIDTH_STEP );
preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
}
}
else if( evt->IsAction( &COMMON_ACTIONS::arcPosture ) )
{
if( clockwise )
aGraphic->SetAngle( aGraphic->GetAngle() - 3600.0 );
else
aGraphic->SetAngle( aGraphic->GetAngle() + 3600.0 );
clockwise = !clockwise;
preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
}
}
m_controls->ShowCursor( false );
m_controls->SetSnapping( false );
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
m_view->Remove( &preview );
return ( step > SET_ORIGIN );
2014-02-11 16:15:33 +00:00
}
int DRAWING_TOOL::drawZone( bool aKeepout )
2014-02-13 11:46:39 +00:00
{
2014-02-24 10:17:49 +00:00
ZONE_CONTAINER* zone = NULL;
DRAWSEGMENT line45;
2014-02-24 10:17:49 +00:00
DRAWSEGMENT* helperLine = NULL; // we will need more than one helper line
2016-06-21 15:06:28 +00:00
BOARD_COMMIT commit( m_frame );
2014-02-13 11:46:39 +00:00
// Add a VIEW_GROUP that serves as a preview for the new item
KIGFX::VIEW_GROUP preview( m_view );
m_view->Add( &preview );
2014-02-13 11:46:39 +00:00
m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear, true );
m_controls->ShowCursor( true );
m_controls->SetSnapping( true );
2014-02-13 11:46:39 +00:00
Activate();
VECTOR2I origin;
int numPoints = 0;
bool direction45 = false; // 45 degrees only mode
2014-02-13 11:46:39 +00:00
// Main loop: keep receiving events
while( OPT_TOOL_EVENT evt = Wait() )
{
bool updatePreview = false; // should preview be updated
VECTOR2I cursorPos = m_controls->GetCursorPosition();
2014-02-13 11:46:39 +00:00
// Enable 45 degrees lines only mode by holding control
if( direction45 != ( evt->Modifier( MD_CTRL ) && numPoints > 0 ) )
{
direction45 = evt->Modifier( MD_CTRL );
if( direction45 )
{
preview.Add( &line45 );
make45DegLine( helperLine, &line45 );
}
else
{
preview.Remove( &line45 );
helperLine->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
}
updatePreview = true;
}
if( evt->IsCancel() || evt->IsActivate() )
2014-02-13 11:46:39 +00:00
{
if( numPoints > 0 ) // cancel the current zone
{
delete zone;
2014-03-18 10:33:49 +00:00
zone = NULL;
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
if( direction45 )
2014-03-18 10:33:49 +00:00
{
preview.Remove( &line45 );
2014-03-18 10:33:49 +00:00
direction45 = false;
}
preview.FreeItems();
updatePreview = true;
numPoints = 0;
}
else // there is no zone currently drawn - just stop the tool
break;
if( evt->IsActivate() ) // now finish unconditionally
break;
2014-02-13 11:46:39 +00:00
}
2015-07-03 18:58:13 +00:00
else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) )
2014-02-13 11:46:39 +00:00
{
// Check if it is double click / closing line (so we have to finish the zone)
if( evt->IsDblClick( BUT_LEFT ) || ( numPoints > 0 && cursorPos == origin ) )
2014-02-13 11:46:39 +00:00
{
if( numPoints > 2 ) // valid zone consists of more than 2 points
{
assert( zone->GetNumCorners() > 2 );
// Finish the zone
if( direction45 )
zone->AppendCorner( cursorPos == origin ? line45.GetStart() : line45.GetEnd() );
zone->Outline()->CloseLastContour();
zone->Outline()->RemoveNullSegments();
if( !aKeepout )
static_cast<PCB_EDIT_FRAME*>( m_frame )->Fill_Zone( zone );
2016-06-21 15:06:28 +00:00
commit.Add( zone );
commit.Push( _( "Draw a zone" ) );
2014-02-17 13:53:01 +00:00
zone = NULL;
}
else
2014-03-18 10:33:49 +00:00
{
delete zone;
2014-03-18 10:33:49 +00:00
zone = NULL;
}
numPoints = 0;
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
2014-03-18 10:33:49 +00:00
if( direction45 )
{
preview.Remove( &line45 );
direction45 = false;
}
preview.FreeItems();
updatePreview = true;
2014-02-13 11:46:39 +00:00
}
else
2014-02-13 11:46:39 +00:00
{
if( numPoints == 0 ) // it's the first click
{
// Get the current default settings for zones
ZONE_SETTINGS zoneInfo = m_frame->GetZoneSettings();
zoneInfo.m_CurrentZone_Layer = m_frame->GetScreen()->m_Active_Layer;
2016-01-07 22:33:31 +00:00
zoneInfo.SetIsKeepout( aKeepout );
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( true );
// Show options dialog
ZONE_EDIT_T dialogResult;
if( aKeepout )
dialogResult = InvokeKeepoutAreaEditor( m_frame, &zoneInfo );
else
{
if( IsCopperLayer( zoneInfo.m_CurrentZone_Layer ) )
dialogResult = InvokeCopperZonesEditor( m_frame, &zoneInfo );
else
dialogResult = InvokeNonCopperZonesEditor( m_frame, NULL, &zoneInfo );
}
if( dialogResult == ZONE_ABORT )
{
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
continue;
}
// Apply the selected settings
zone = new ZONE_CONTAINER( m_board );
zoneInfo.ExportSetting( *zone );
m_frame->GetGalCanvas()->SetTopLayer( zoneInfo.m_CurrentZone_Layer );
// Add the first point
zone->Outline()->Start( zoneInfo.m_CurrentZone_Layer,
cursorPos.x, cursorPos.y,
zone->GetHatchStyle() );
origin = cursorPos;
// Helper line represents the currently drawn line of the zone polygon
helperLine = new DRAWSEGMENT;
helperLine->SetShape( S_SEGMENT );
helperLine->SetWidth( 1 );
helperLine->SetLayer( zoneInfo.m_CurrentZone_Layer );
helperLine->SetStart( wxPoint( cursorPos.x, cursorPos.y ) );
helperLine->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
line45 = *helperLine;
preview.Add( helperLine );
}
else
{
zone->AppendCorner( helperLine->GetEnd() );
helperLine = new DRAWSEGMENT( *helperLine );
helperLine->SetStart( helperLine->GetEnd() );
preview.Add( helperLine );
}
++numPoints;
updatePreview = true;
2014-02-13 11:46:39 +00:00
}
}
else if( evt->IsMotion() && numPoints > 0 )
2014-02-13 11:46:39 +00:00
{
// 45 degree lines
if( direction45 )
make45DegLine( helperLine, &line45 );
else
helperLine->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
// Show a preview of the item
updatePreview = true;
2014-02-13 11:46:39 +00:00
}
if( updatePreview )
preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY );
2014-02-13 11:46:39 +00:00
}
m_controls->ShowCursor( false );
m_controls->SetSnapping( false );
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
m_view->Remove( &preview );
2014-02-13 11:46:39 +00:00
m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString );
2014-02-13 11:46:39 +00:00
return 0;
}
void DRAWING_TOOL::make45DegLine( DRAWSEGMENT* aSegment, DRAWSEGMENT* aHelper ) const
{
VECTOR2I cursorPos = m_controls->GetCursorPosition();
VECTOR2I origin( aSegment->GetStart() );
DIRECTION_45 direction( origin - cursorPos );
SHAPE_LINE_CHAIN newChain = direction.BuildInitialTrace( origin, cursorPos );
if( newChain.PointCount() > 2 )
{
aSegment->SetEnd( wxPoint( newChain.Point( -2 ).x, newChain.Point( -2 ).y ) );
aHelper->SetStart( wxPoint( newChain.Point( -2 ).x, newChain.Point( -2 ).y ) );
aHelper->SetEnd( wxPoint( newChain.Point( -1 ).x, newChain.Point( -1 ).y ) );
}
else
{
aSegment->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
aHelper->SetStart( wxPoint( cursorPos.x, cursorPos.y ) );
aHelper->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
}
}
void DRAWING_TOOL::SetTransitions()
{
Go( &DRAWING_TOOL::DrawLine, COMMON_ACTIONS::drawLine.MakeEvent() );
Go( &DRAWING_TOOL::DrawCircle, COMMON_ACTIONS::drawCircle.MakeEvent() );
Go( &DRAWING_TOOL::DrawArc, COMMON_ACTIONS::drawArc.MakeEvent() );
Go( &DRAWING_TOOL::DrawDimension, COMMON_ACTIONS::drawDimension.MakeEvent() );
Go( &DRAWING_TOOL::DrawZone, COMMON_ACTIONS::drawZone.MakeEvent() );
Go( &DRAWING_TOOL::DrawKeepout, COMMON_ACTIONS::drawKeepout.MakeEvent() );
Go( &DRAWING_TOOL::PlaceText, COMMON_ACTIONS::placeText.MakeEvent() );
2014-07-09 13:02:56 +00:00
Go( &DRAWING_TOOL::PlaceDXF, COMMON_ACTIONS::placeDXF.MakeEvent() );
Go( &DRAWING_TOOL::SetAnchor, COMMON_ACTIONS::setAnchor.MakeEvent() );
}
int DRAWING_TOOL::getSegmentWidth( unsigned int aLayer ) const
{
assert( m_board );
if( aLayer == Edge_Cuts )
return m_board->GetDesignSettings().m_EdgeSegmentWidth;
else if( m_editModules )
return m_board->GetDesignSettings().m_ModuleSegmentWidth;
else
return m_board->GetDesignSettings().m_DrawSegmentWidth;
}
const int DRAWING_TOOL::WIDTH_STEP = 100000;