/* * KiRouter - a push-and-(sometimes-)shove PCB router * * Copyright (C) 2013-2017 CERN * Copyright (C) 2017 KiCad Developers, see AUTHORS.txt for contributors. * Author: Tomasz Wlostowski * * 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 3 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, see . */ #include #include #include using namespace std::placeholders; #include "class_draw_panel_gal.h" #include "class_board.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "router_tool.h" #include "pns_segment.h" #include "pns_router.h" using namespace KIGFX; /** * Flags used by via tool actions */ enum VIA_ACTION_FLAGS { // Via type VIA_MASK = 0x03, VIA = 0x00, ///> Normal via BLIND_VIA = 0x01, ///> blind/buried via MICROVIA = 0x02, ///> Microvia // Select layer SELECT_LAYER = VIA_MASK + 1, ///> Ask user to select layer before adding via }; TOOL_ACTION PCB_ACTIONS::routerActivateSingle( "pcbnew.InteractiveRouter.SingleTrack", AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_ADD_NEW_TRACK ), _( "Interactive Router (Single Tracks)" ), _( "Run push & shove router (single tracks)" ), ps_router_xpm, AF_ACTIVATE ); TOOL_ACTION PCB_ACTIONS::routerActivateDiffPair( "pcbnew.InteractiveRouter.DiffPair", AS_GLOBAL, '6', _( "Interactive Router (Differential Pairs)" ), _( "Run push & shove router (differential pairs)" ), ps_diff_pair_xpm, AF_ACTIVATE ); TOOL_ACTION PCB_ACTIONS::routerActivateSettingsDialog( "pcbnew.InteractiveRouter.SettingsDialog", AS_GLOBAL, 0, _( "Interactive Router Settings" ), _( "Open Interactive Router settings" ), NULL, AF_ACTIVATE ); TOOL_ACTION PCB_ACTIONS::routerActivateDpDimensionsDialog( "pcbnew.InteractiveRouter.DpDimensionsDialog", AS_GLOBAL, 0, _( "Differential Pair Dimension settings" ), _( "Open Differential Pair Dimension settings" ), ps_diff_pair_gap_xpm, AF_ACTIVATE ); TOOL_ACTION PCB_ACTIONS::routerActivateTuneSingleTrace( "pcbnew.LengthTuner.TuneSingleTrack", AS_GLOBAL, '7', _( "Tune length of a single track" ), "", ps_tune_length_xpm, AF_ACTIVATE ); TOOL_ACTION PCB_ACTIONS::routerActivateTuneDiffPair( "pcbnew.LengthTuner.TuneDiffPair", AS_GLOBAL, '8', _( "Tune length of a differential pair" ), "", NULL, AF_ACTIVATE ); TOOL_ACTION PCB_ACTIONS::routerActivateTuneDiffPairSkew( "pcbnew.LengthTuner.TuneDiffPairSkew", AS_GLOBAL, '9', _( "Tune skew of a differential pair" ), "", NULL, AF_ACTIVATE ); TOOL_ACTION PCB_ACTIONS::routerInlineDrag( "pcbnew.InteractiveRouter.InlineDrag", AS_CONTEXT, 0, _( "Drag Track/Via" ), _( "Drags tracks and vias without breaking connections" ), drag_xpm ); TOOL_ACTION PCB_ACTIONS::breakTrack( "pcbnew.InteractiveRouter.BreakTrack", AS_GLOBAL, 0, _( "Break Track" ), _( "Splits the track segment into two segments connected at the cursor position." ), break_line_xpm ); TOOL_ACTION PCB_ACTIONS::drag45Degree( "pcbnew.InteractiveRouter.Drag45Degree", AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_DRAG_TRACK_KEEP_SLOPE ), _( "Drag (45 degree mode)" ), _( "todo" ), drag_segment_withslope_xpm ); TOOL_ACTION PCB_ACTIONS::dragFreeAngle( "pcbnew.InteractiveRouter.DragFreeAngle", AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_DRAG_ITEM ), _( "Drag (free angle)" ), _( "todo" ), move_xpm ); static const TOOL_ACTION ACT_NewTrack( "pcbnew.InteractiveRouter.NewTrack", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_ADD_NEW_TRACK ), _( "New Track" ), _( "Starts laying a new track." ), add_tracks_xpm ); static const TOOL_ACTION ACT_EndTrack( "pcbnew.InteractiveRouter.EndTrack", AS_CONTEXT, WXK_END, _( "End Track" ), _( "Stops laying the current track." ), checked_ok_xpm ); static const TOOL_ACTION ACT_AutoEndRoute( "pcbnew.InteractiveRouter.AutoEndRoute", AS_CONTEXT, 'F', _( "Auto-end Track" ), _( "Automagically finishes currently routed track." ) ); static const TOOL_ACTION ACT_PlaceThroughVia( "pcbnew.InteractiveRouter.PlaceVia", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_ADD_THROUGH_VIA ), _( "Place Through Via" ), _( "Adds a through-hole via at the end of currently routed track." ), via_xpm, AF_NONE, (void*) VIA_ACTION_FLAGS::VIA ); static const TOOL_ACTION ACT_PlaceBlindVia( "pcbnew.InteractiveRouter.PlaceBlindVia", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_ADD_BLIND_BURIED_VIA ), _( "Place Blind/Buried Via" ), _( "Adds a blind or buried via at the end of currently routed track."), via_buried_xpm, AF_NONE, (void*) VIA_ACTION_FLAGS::BLIND_VIA ); static const TOOL_ACTION ACT_PlaceMicroVia( "pcbnew.InteractiveRouter.PlaceMicroVia", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_ADD_MICROVIA ), _( "Place Microvia" ), _( "Adds a microvia at the end of currently routed track." ), via_microvia_xpm, AF_NONE, (void*) VIA_ACTION_FLAGS::MICROVIA ); static const TOOL_ACTION ACT_SelLayerAndPlaceThroughVia( "pcbnew.InteractiveRouter.SelLayerAndPlaceVia", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_SEL_LAYER_AND_ADD_THROUGH_VIA ), _( "Select Layer and Place Through Via" ), _( "Select a layer, then add a through-hole via at the end of currently routed track." ), select_w_layer_xpm, AF_NONE, (void*) ( VIA_ACTION_FLAGS::VIA | VIA_ACTION_FLAGS::SELECT_LAYER ) ); static const TOOL_ACTION ACT_SelLayerAndPlaceBlindVia( "pcbnew.InteractiveRouter.SelLayerAndPlaceBlindVia", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_SEL_LAYER_AND_ADD_BLIND_BURIED_VIA ), _( "Select Layer and Place Blind/Buried Via" ), _( "Select a layer, then add a blind or buried via at the end of currently routed track."), select_w_layer_xpm, AF_NONE, (void*) ( VIA_ACTION_FLAGS::BLIND_VIA | VIA_ACTION_FLAGS::SELECT_LAYER ) ); static const TOOL_ACTION ACT_CustomTrackWidth( "pcbnew.InteractiveRouter.CustomTrackViaSize", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_CUSTOM_TRACK_WIDTH ), _( "Custom Track/Via Size" ), _( "Shows a dialog for changing the track width and via size." ), width_track_xpm ); static const TOOL_ACTION ACT_SwitchPosture( "pcbnew.InteractiveRouter.SwitchPosture", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_SWITCH_TRACK_POSTURE ), _( "Switch Track Posture" ), _( "Switches posture of the currently routed track." ), change_entry_orient_xpm ); static const TOOL_ACTION ACT_SetDpDimensions( "pcbnew.InteractiveRouter.SetDpDimensions", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_DP_DIMENSIONS ), _( "Differential Pair Dimensions..." ), _( "Sets the width and gap of the currently routed differential pair." ), ps_diff_pair_tune_length_xpm ); ROUTER_TOOL::ROUTER_TOOL() : TOOL_BASE( "pcbnew.InteractiveRouter" ) { } class TRACK_WIDTH_MENU: public TRACK_VIA_SIZE_MENU { public: TRACK_WIDTH_MENU( const BOARD* aBoard ) : TRACK_VIA_SIZE_MENU( true, true ) { SetTitle( _( "Select Track/Via Width" ) ); SetBoard( aBoard ); } void SetBoard( const BOARD* aBoard ) { m_board = aBoard; Clear(); Append( ID_POPUP_PCB_SELECT_CUSTOM_WIDTH, _( "Custom size" ), wxEmptyString, wxITEM_CHECK ); Append( ID_POPUP_PCB_SELECT_AUTO_WIDTH, _( "Use the starting track width" ), _( "Route using the width of the starting track." ), wxITEM_CHECK ); Append( ID_POPUP_PCB_SELECT_USE_NETCLASS_VALUES, _( "Use net class values" ), _( "Use track and via sizes from the net class" ), wxITEM_CHECK ); AppendSeparator(); // Append the list of tracks & via sizes AppendSizes( aBoard ); } protected: CONTEXT_MENU* create() const override { return new TRACK_WIDTH_MENU( m_board ); } OPT_TOOL_EVENT eventHandler( const wxMenuEvent& aEvent ) override { BOARD_DESIGN_SETTINGS &bds = m_board->GetDesignSettings(); int id = aEvent.GetId(); // On Windows, this handler can be called with a non existing event ID not existing // in any menuitem. // So we keep trace of in-range/out-of-range event ID bool in_range = true; // Initial settings, to be modified below, but only if the ID exists in this menu bool useConnectedTrackWidth = false; bool useCustomTrackViaSize = false; if( id == ID_POPUP_PCB_SELECT_CUSTOM_WIDTH ) { useCustomTrackViaSize = true; } else if( id == ID_POPUP_PCB_SELECT_AUTO_WIDTH ) { useConnectedTrackWidth = true; } else if( id == ID_POPUP_PCB_SELECT_USE_NETCLASS_VALUES ) { bds.SetViaSizeIndex( 0 ); bds.SetTrackWidthIndex( 0 ); } else if( id >= ID_POPUP_PCB_SELECT_VIASIZE1 && id <= ID_POPUP_PCB_SELECT_VIASIZE16 ) { // via size has changed bds.SetViaSizeIndex( id - ID_POPUP_PCB_SELECT_VIASIZE1 ); } else if( id >= ID_POPUP_PCB_SELECT_WIDTH1 && id <= ID_POPUP_PCB_SELECT_WIDTH16 ) { // track width has changed bds.SetTrackWidthIndex( id - ID_POPUP_PCB_SELECT_WIDTH1 ); } else { in_range = false; // This event ID does not exist in the menu wxASSERT_MSG( false, "OPT_TOOL_EVENT EventHandler: unexpected id" ); // Fix me: How to return this error as OPT_TOOL_EVENT? } if( in_range ) { // Update this setup only id the event ID matches the options of this menu bds.m_UseConnectedTrackWidth = useConnectedTrackWidth; bds.UseCustomTrackViaSize( useCustomTrackViaSize ); } return OPT_TOOL_EVENT( PCB_ACTIONS::trackViaSizeChanged.MakeEvent() ); } private: const BOARD* m_board; }; class ROUTER_TOOL_MENU : public CONTEXT_MENU { public: ROUTER_TOOL_MENU( const BOARD* aBoard, PCB_EDIT_FRAME& aFrame, PNS::ROUTER_MODE aMode ) : m_board( aBoard ), m_frame( aFrame ), m_mode( aMode ), m_widthMenu( aBoard ), m_zoomMenu( &aFrame ), m_gridMenu( &aFrame ) { SetTitle( _( "Interactive Router" ) ); Add( ACTIONS::cancelInteractive ); AppendSeparator(); Add( ACT_NewTrack ); Add( ACT_EndTrack ); Add( PCB_ACTIONS::breakTrack ); Add( PCB_ACTIONS::drag45Degree ); Add( PCB_ACTIONS::dragFreeAngle ); // Add( ACT_AutoEndRoute ); // fixme: not implemented yet. Sorry. Add( ACT_PlaceThroughVia ); Add( ACT_PlaceBlindVia ); Add( ACT_PlaceMicroVia ); Add( ACT_SelLayerAndPlaceThroughVia ); Add( ACT_SelLayerAndPlaceBlindVia ); Add( ACT_SwitchPosture ); AppendSeparator(); m_widthMenu.SetBoard( aBoard ); Add( &m_widthMenu ); Add( ACT_CustomTrackWidth ); if( aMode == PNS::PNS_MODE_ROUTE_DIFF_PAIR ) Add( ACT_SetDpDimensions ); AppendSeparator(); Add( PNS::TOOL_BASE::ACT_RouterOptions ); AppendSeparator(); Add( &m_zoomMenu ); Add( &m_gridMenu ); } private: CONTEXT_MENU* create() const override { return new ROUTER_TOOL_MENU( m_board, m_frame, m_mode ); } const BOARD* m_board; PCB_EDIT_FRAME& m_frame; PNS::ROUTER_MODE m_mode; TRACK_WIDTH_MENU m_widthMenu; ZOOM_MENU m_zoomMenu; GRID_MENU m_gridMenu; }; ROUTER_TOOL::~ROUTER_TOOL() { m_savedSettings.Save( GetSettings() ); } bool ROUTER_TOOL::Init() { m_savedSettings.Load( GetSettings() ); return true; } void ROUTER_TOOL::Reset( RESET_REASON aReason ) { TOOL_BASE::Reset( aReason ); } int ROUTER_TOOL::getDefaultWidth( int aNetCode ) { int w, d1, d2; getNetclassDimensions( aNetCode, w, d1, d2 ); return w; } void ROUTER_TOOL::getNetclassDimensions( int aNetCode, int& aWidth, int& aViaDiameter, int& aViaDrill ) { BOARD_DESIGN_SETTINGS &bds = board()->GetDesignSettings(); NETCLASSPTR netClass; NETINFO_ITEM* ni = board()->FindNet( aNetCode ); if( ni ) { wxString netClassName = ni->GetClassName(); netClass = bds.m_NetClasses.Find( netClassName ); } if( !netClass ) netClass = bds.GetDefault(); aWidth = netClass->GetTrackWidth(); aViaDiameter = netClass->GetViaDiameter(); aViaDrill = netClass->GetViaDrill(); } void ROUTER_TOOL::handleCommonEvents( const TOOL_EVENT& aEvent ) { #ifdef DEBUG if( aEvent.IsKeyPressed() ) { switch( aEvent.KeyCode() ) { case '0': wxLogTrace( "PNS", "saving drag/route log...\n" ); m_router->DumpLog(); break; } } #endif } int ROUTER_TOOL::getStartLayer( const PNS::ITEM* aItem ) { int tl = getView()->GetTopLayer(); if( m_startItem ) { const LAYER_RANGE& ls = m_startItem->Layers(); if( ls.Overlaps( tl ) ) return tl; else return ls.Start(); } return tl; } void ROUTER_TOOL::switchLayerOnViaPlacement() { int al = frame()->GetActiveLayer(); int cl = m_router->GetCurrentLayer(); if( cl != al ) { m_router->SwitchLayer( al ); } OPT newLayer = m_router->Sizes().PairedLayer( cl ); if( !newLayer ) newLayer = m_router->Sizes().GetLayerTop(); m_router->SwitchLayer( *newLayer ); frame()->SetActiveLayer( ToLAYER_ID( *newLayer ) ); } static VIATYPE_T getViaTypeFromFlags( int aFlags ) { VIATYPE_T viaType = VIA_THROUGH; switch( aFlags & VIA_ACTION_FLAGS::VIA_MASK ) { case VIA_ACTION_FLAGS::VIA: viaType = VIA_THROUGH; break; case VIA_ACTION_FLAGS::BLIND_VIA: viaType = VIA_BLIND_BURIED; break; case VIA_ACTION_FLAGS::MICROVIA: viaType = VIA_MICROVIA; break; default: wxASSERT_MSG( false, "Unhandled via type" ); } return viaType; } int ROUTER_TOOL::onViaCommand( const TOOL_EVENT& aEvent ) { const int actViaFlags = aEvent.Parameter(); VIATYPE_T viaType = getViaTypeFromFlags( actViaFlags ); const bool selectLayer = actViaFlags & VIA_ACTION_FLAGS::SELECT_LAYER; BOARD_DESIGN_SETTINGS& bds = board()->GetDesignSettings(); const int layerCount = bds.GetCopperLayerCount(); int currentLayer = m_router->GetCurrentLayer(); PCB_LAYER_ID pairTop = frame()->GetScreen()->m_Route_Layer_TOP; PCB_LAYER_ID pairBottom = frame()->GetScreen()->m_Route_Layer_BOTTOM; PNS::SIZES_SETTINGS sizes = m_router->Sizes(); // ask the user for a target layer PCB_LAYER_ID targetLayer = UNDEFINED_LAYER; if( selectLayer ) { wxPoint dlgPosition = wxGetMousePosition(); targetLayer = frame()->SelectLayer( static_cast( currentLayer ), LSET::AllNonCuMask(), dlgPosition ); } // fixme: P&S supports more than one fixed layer pair. Update the dialog? sizes.ClearLayerPairs(); if( !m_router->IsPlacingVia() ) { // Cannot place microvias or blind vias if not allowed (obvious) if( ( viaType == VIA_BLIND_BURIED ) && ( !bds.m_BlindBuriedViaAllowed ) ) { DisplayError( frame(), _( "Blind/buried vias have to be enabled in the design settings." ) ); return false; } if( ( viaType == VIA_MICROVIA ) && ( !bds.m_MicroViasAllowed ) ) { DisplayError( frame(), _( "Microvias have to be enabled in the design settings." ) ); return false; } // Can only place through vias on 2-layer boards if( ( viaType != VIA_THROUGH ) && ( layerCount <= 2 ) ) { DisplayError( frame(), _( "Only through vias are allowed on 2 layer boards." ) ); return false; } // Can only place microvias if we're on an outer layer, or directly adjacent to one if( ( viaType == VIA_MICROVIA ) && ( currentLayer > In1_Cu ) && ( currentLayer < layerCount - 2 ) ) { DisplayError( frame(), _( "Microvias can be placed only between the outer layers " \ "(F.Cu/B.Cu) and the ones directly adjacent to them." ) ); return false; } } // Convert blind/buried via to a through hole one, if it goes through all layers if( viaType == VIA_BLIND_BURIED && ( ( currentLayer == B_Cu ) || ( currentLayer == F_Cu ) ) && ( ( pairTop == B_Cu && pairBottom == F_Cu ) || ( pairTop == F_Cu && pairBottom == B_Cu ) ) ) { viaType = VIA_THROUGH; } switch( viaType ) { case VIA_THROUGH: sizes.SetViaDiameter( bds.GetCurrentViaSize() ); sizes.SetViaDrill( bds.GetCurrentViaDrill() ); if( targetLayer != UNDEFINED_LAYER ) { // go from the current layer to the chosen layer sizes.AddLayerPair( currentLayer, targetLayer ); } else { // use the default layer pair sizes.AddLayerPair( pairTop, pairBottom ); } break; case VIA_MICROVIA: sizes.SetViaDiameter( bds.GetCurrentMicroViaSize() ); sizes.SetViaDrill( bds.GetCurrentMicroViaDrill() ); wxASSERT_MSG( !selectLayer, "Unexpected select layer for microvia (microvia layers are implicit)" ); if( currentLayer == F_Cu || currentLayer == In1_Cu ) { // front-side microvia sizes.AddLayerPair( F_Cu, In1_Cu ); } else if( currentLayer == B_Cu || currentLayer == layerCount - 2 ) { // back-side microvia sizes.AddLayerPair( B_Cu, layerCount - 2 ); } else { wxASSERT_MSG( false, "Invalid layer pair for microvia (must be on or adjacent to an outer layer)" ); } break; case VIA_BLIND_BURIED: sizes.SetViaDiameter( bds.GetCurrentViaSize() ); sizes.SetViaDrill( bds.GetCurrentViaDrill() ); if( targetLayer != UNDEFINED_LAYER ) { // go directly to the user specified layer sizes.AddLayerPair( currentLayer, targetLayer ); } else { if( currentLayer == pairTop || currentLayer == pairBottom ) { // the current layer is on the defined layer pair, // swap to the other side sizes.AddLayerPair( pairTop, pairBottom ); } else { // the current layer is not part of the current layer pair, // so fallback and swap to the top layer of the pair by default sizes.AddLayerPair( pairTop, currentLayer ); } } break; default: wxASSERT( false ); break; } sizes.SetViaType( viaType ); m_router->UpdateSizes( sizes ); m_router->ToggleViaPlacement(); updateEndItem( aEvent ); m_router->Move( m_endSnapPoint, m_endItem ); // refresh return 0; } bool ROUTER_TOOL::prepareInteractive() { int routingLayer = getStartLayer( m_startItem ); if( !IsCopperLayer( routingLayer ) ) { DisplayError( frame(), _( "Tracks on Copper layers only" ) ); return false; } frame()->SetActiveLayer( ToLAYER_ID( routingLayer ) ); // fixme: switch on invisible layer // for some reason I don't understand, GetNetclass() may return null sometimes... if( m_startItem && m_startItem->Net() >= 0 && m_startItem->Parent() && m_startItem->Parent()->GetNetClass() ) { highlightNet( true, m_startItem->Net() ); // Update track width and via size shown in main toolbar comboboxes frame()->SetCurrentNetClass( m_startItem->Parent()->GetNetClass()->GetName() ); } else frame()->SetCurrentNetClass( NETCLASS::Default ); controls()->ForceCursorPosition( false ); controls()->SetAutoPan( true ); PNS::SIZES_SETTINGS sizes( m_router->Sizes() ); sizes.Init( board(), m_startItem ); sizes.AddLayerPair( frame()->GetScreen()->m_Route_Layer_TOP, frame()->GetScreen()->m_Route_Layer_BOTTOM ); m_router->UpdateSizes( sizes ); if( !m_router->StartRouting( m_startSnapPoint, m_startItem, routingLayer ) ) { DisplayError( frame(), m_router->FailureReason() ); highlightNet( false ); return false; } m_endItem = NULL; m_endSnapPoint = m_startSnapPoint; frame()->UndoRedoBlock( true ); return true; } bool ROUTER_TOOL::finishInteractive() { m_router->StopRouting(); controls()->SetAutoPan( false ); controls()->ForceCursorPosition( false ); frame()->UndoRedoBlock( false ); highlightNet( false ); return true; } void ROUTER_TOOL::performRouting() { if( !prepareInteractive() ) return; while( OPT_TOOL_EVENT evt = Wait() ) { // Don't crash if we missed an operation that cancelled routing. wxCHECK2( m_router->RoutingInProgress(), break ); if( evt->IsMotion() ) { m_router->SetOrthoMode( evt->Modifier( MD_CTRL ) ); updateEndItem( *evt ); m_router->Move( m_endSnapPoint, m_endItem ); } else if( evt->IsClick( BUT_LEFT ) ) { updateEndItem( *evt ); bool needLayerSwitch = m_router->IsPlacingVia(); if( m_router->FixRoute( m_endSnapPoint, m_endItem ) ) break; if( needLayerSwitch ) switchLayerOnViaPlacement(); // Synchronize the indicated layer frame()->SetActiveLayer( ToLAYER_ID( m_router->GetCurrentLayer() ) ); updateEndItem( *evt ); m_router->Move( m_endSnapPoint, m_endItem ); m_startItem = NULL; } else if( evt->IsAction( &ACT_SwitchPosture ) ) { m_router->FlipPosture(); updateEndItem( *evt ); m_router->Move( m_endSnapPoint, m_endItem ); // refresh } else if( evt->IsAction( &PCB_ACTIONS::layerChanged ) ) { m_router->SwitchLayer( frame()->GetActiveLayer() ); updateEndItem( *evt ); m_router->Move( m_endSnapPoint, m_endItem ); // refresh } else if( evt->IsAction( &ACT_EndTrack ) ) { bool still_routing = true; while( still_routing ) still_routing = m_router->FixRoute( m_endSnapPoint, m_endItem ); break; } else if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) || evt->IsUndoRedo() || evt->IsAction( &PCB_ACTIONS::routerInlineDrag ) ) break; } finishInteractive(); } int ROUTER_TOOL::DpDimensionsDialog( const TOOL_EVENT& aEvent ) { Activate(); PNS::SIZES_SETTINGS sizes = m_router->Sizes(); DIALOG_PNS_DIFF_PAIR_DIMENSIONS settingsDlg( frame(), sizes ); if( settingsDlg.ShowModal() ) { m_router->UpdateSizes( sizes ); m_savedSizes = sizes; } return 0; } int ROUTER_TOOL::SettingsDialog( const TOOL_EVENT& aEvent ) { Activate(); DIALOG_PNS_SETTINGS settingsDlg( frame(), m_router->Settings() ); if( settingsDlg.ShowModal() ) { m_savedSettings = m_router->Settings(); } return 0; } void ROUTER_TOOL::setTransitions() { Go( &ROUTER_TOOL::RouteSingleTrace, PCB_ACTIONS::routerActivateSingle.MakeEvent() ); Go( &ROUTER_TOOL::RouteDiffPair, PCB_ACTIONS::routerActivateDiffPair.MakeEvent() ); Go( &ROUTER_TOOL::DpDimensionsDialog, PCB_ACTIONS::routerActivateDpDimensionsDialog.MakeEvent() ); Go( &ROUTER_TOOL::SettingsDialog, PCB_ACTIONS::routerActivateSettingsDialog.MakeEvent() ); Go( &ROUTER_TOOL::InlineDrag, PCB_ACTIONS::routerInlineDrag.MakeEvent() ); Go( &ROUTER_TOOL::onViaCommand, ACT_PlaceThroughVia.MakeEvent() ); Go( &ROUTER_TOOL::onViaCommand, ACT_PlaceBlindVia.MakeEvent() ); Go( &ROUTER_TOOL::onViaCommand, ACT_PlaceMicroVia.MakeEvent() ); Go( &ROUTER_TOOL::onViaCommand, ACT_SelLayerAndPlaceThroughVia.MakeEvent() ); Go( &ROUTER_TOOL::onViaCommand, ACT_SelLayerAndPlaceBlindVia.MakeEvent() ); // TODO is not this redundant? the same actions can be used for menus and hotkeys Go( &ROUTER_TOOL::SettingsDialog, ACT_RouterOptions.MakeEvent() ); Go( &ROUTER_TOOL::DpDimensionsDialog, ACT_SetDpDimensions.MakeEvent() ); Go( &ROUTER_TOOL::CustomTrackWidthDialog, ACT_CustomTrackWidth.MakeEvent() ); Go( &ROUTER_TOOL::onTrackViaSizeChanged, PCB_ACTIONS::trackViaSizeChanged.MakeEvent() ); } int ROUTER_TOOL::RouteSingleTrace( const TOOL_EVENT& aEvent ) { frame()->SetToolID( ID_TRACK_BUTT, wxCURSOR_PENCIL, _( "Route Track" ) ); return mainLoop( PNS::PNS_MODE_ROUTE_SINGLE ); } int ROUTER_TOOL::RouteDiffPair( const TOOL_EVENT& aEvent ) { frame()->SetToolID( ID_TRACK_BUTT, wxCURSOR_PENCIL, _( "Router Differential Pair" ) ); return mainLoop( PNS::PNS_MODE_ROUTE_DIFF_PAIR ); } void ROUTER_TOOL::breakTrack() { if ( m_startItem->OfKind( PNS::ITEM::SEGMENT_T ) ) { m_router->BreakSegment( m_startItem, m_startSnapPoint ); } } int ROUTER_TOOL::mainLoop( PNS::ROUTER_MODE aMode ) { PCB_EDIT_FRAME* frame = getEditFrame(); BOARD* board = getModel(); // Deselect all items m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true ); Activate(); m_router->SetMode( aMode ); controls()->ShowCursor( true ); m_startSnapPoint = getViewControls()->GetCursorPosition(); std::unique_ptr ctxMenu( new ROUTER_TOOL_MENU( board, *frame, aMode ) ); SetContextMenu( ctxMenu.get() ); // Main loop: keep receiving events while( OPT_TOOL_EVENT evt = Wait() ) { if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) ) { break; // Finish } else if( evt->Action() == TA_UNDO_REDO_PRE ) { m_router->ClearWorld(); } else if( evt->Action() == TA_UNDO_REDO_POST || evt->Action() == TA_MODEL_CHANGE ) { m_router->SyncWorld(); } else if( evt->IsMotion() ) { updateStartItem( *evt ); } else if( evt->IsAction( &PCB_ACTIONS::dragFreeAngle ) ) { updateStartItem( *evt ); performDragging( PNS::DM_ANY | PNS::DM_FREE_ANGLE ); } else if( evt->IsAction( &PCB_ACTIONS::drag45Degree ) ) { updateStartItem( *evt ); performDragging( PNS::DM_ANY ); } else if( evt->IsAction( &PCB_ACTIONS::breakTrack ) ) { updateStartItem( *evt ); breakTrack( ); } else if( evt->IsClick( BUT_LEFT ) || evt->IsAction( &ACT_NewTrack ) ) { updateStartItem( *evt ); if( evt->Modifier( MD_CTRL ) ) performDragging( PNS::DM_ANY ); else performRouting(); } else if( evt->IsAction( &ACT_PlaceThroughVia ) ) { m_toolMgr->RunAction( PCB_ACTIONS::layerToggle, true ); } } frame->SetNoToolSelected(); SetContextMenu( nullptr ); // Store routing settings till the next invocation m_savedSettings = m_router->Settings(); m_savedSizes = m_router->Sizes(); return 0; } void ROUTER_TOOL::performDragging( int aMode ) { VIEW_CONTROLS* ctls = getViewControls(); if( m_startItem && m_startItem->IsLocked() ) { if( !IsOK( frame(), _( "The item is locked. Do you want to continue?" ) ) ) return; } bool dragStarted = m_router->StartDragging( m_startSnapPoint, m_startItem, aMode ); if( !dragStarted ) return; if( m_startItem && m_startItem->Net() >= 0 ) highlightNet( true, m_startItem->Net() ); ctls->SetAutoPan( true ); frame()->UndoRedoBlock( true ); while( OPT_TOOL_EVENT evt = Wait() ) { ctls->ForceCursorPosition( false ); if( evt->IsMotion() ) { updateEndItem( *evt ); m_router->Move( m_endSnapPoint, m_endItem ); } else if( evt->IsClick( BUT_LEFT ) ) { if( m_router->FixRoute( m_endSnapPoint, m_endItem ) ) break; } else if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) || evt->IsUndoRedo() ) break; handleCommonEvents( *evt ); } if( m_router->RoutingInProgress() ) m_router->StopRouting(); m_startItem = nullptr; frame()->UndoRedoBlock( false ); ctls->SetAutoPan( false ); ctls->ForceCursorPosition( false ); highlightNet( false ); } void ROUTER_TOOL::NeighboringSegmentFilter( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) { /* If the collection contains a trivial line corner (two connected segments) * or a non-fanout-via (a via with no more than two connected segments), then * trim the collection down to a single item (which one won't matter since * they're all connected). */ // First make sure we've got something that *might* match. int vias = aCollector.CountType( PCB_VIA_T ); int traces = aCollector.CountType( PCB_TRACE_T ); if( vias > 1 || traces > 2 || vias + traces < 1 ) return; // Fetch first TRACK (via or trace) as our reference TRACK* reference = nullptr; for( int i = 0; !reference && i < aCollector.GetCount(); i++ ) reference = dynamic_cast( aCollector[i] ); int refNet = reference->GetNetCode(); wxPoint refPoint; STATUS_FLAGS flags = reference->IsPointOnEnds( wxPoint( aPt.x, aPt.y ), -1 ); if( flags & STARTPOINT ) refPoint = reference->GetStart(); else if( flags & ENDPOINT ) refPoint = reference->GetEnd(); else return; // Check all items to ensure that they are TRACKs which are co-terminus // with the reference, and on the same net. Ignore markers. for( int i = 0; i < aCollector.GetCount(); i++ ) { BOARD_ITEM* item = static_cast( aCollector[i] ); if( item->Type() == PCB_MARKER_T ) continue; TRACK* neighbor = dynamic_cast( item ); if( !neighbor || neighbor->GetNetCode() != refNet ) return; if( neighbor->GetStart() != refPoint && neighbor->GetEnd() != refPoint ) return; } // Selection meets criteria; trim it to the reference item. for( int i = aCollector.GetCount()-1; i >= 0; i-- ) { if( dynamic_cast( aCollector[i] ) != reference ) aCollector.Remove( i ); } } bool ROUTER_TOOL::CanInlineDrag() { m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true, NeighboringSegmentFilter ); const auto& selection = m_toolMgr->GetTool()->GetSelection(); if( selection.Size() == 1 ) { const BOARD_CONNECTED_ITEM* item = static_cast( selection.Front() ); if( item->Type() == PCB_TRACE_T || item->Type() == PCB_VIA_T ) return true; } m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor ); // restore selection to unfiltered state return false; } int ROUTER_TOOL::InlineDrag( const TOOL_EVENT& aEvent ) { // Get the item under the cursor m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true, NeighboringSegmentFilter ); const auto& selection = m_toolMgr->GetTool()->GetSelection(); if( selection.Size() != 1 ) return 0; const BOARD_CONNECTED_ITEM* item = static_cast( selection.Front() ); if( item->Type() != PCB_TRACE_T && item->Type() != PCB_VIA_T ) return 0; Activate(); m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true ); m_router->SyncWorld(); m_startItem = m_router->GetWorld()->FindItemByParent( item ); if( m_startItem && m_startItem->IsLocked() ) { if( !IsOK( frame(), _( "The item is locked. Do you want to continue?" ) ) ) return false; } VECTOR2I p0 = controls()->GetCursorPosition(); int dragMode = aEvent.Parameter (); bool dragStarted = m_router->StartDragging( p0, m_startItem, dragMode ); if( !dragStarted ) return 0; controls()->ShowCursor( true ); controls()->ForceCursorPosition( false ); controls()->SetAutoPan( true ); frame()->UndoRedoBlock( true ); while( OPT_TOOL_EVENT evt = Wait() ) { if( evt->IsCancel() ) { break; } else if( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) { updateEndItem( *evt ); m_router->Move( m_endSnapPoint, m_endItem ); } else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) ) { updateEndItem( *evt ); m_router->FixRoute( m_endSnapPoint, m_endItem ); break; } } if( m_router->RoutingInProgress() ) m_router->StopRouting(); frame()->UndoRedoBlock( false ); return 0; } int ROUTER_TOOL::CustomTrackWidthDialog( const TOOL_EVENT& aEvent ) { BOARD_DESIGN_SETTINGS& bds = board()->GetDesignSettings(); DIALOG_TRACK_VIA_SIZE sizeDlg( frame(), bds ); if( sizeDlg.ShowModal() ) { bds.UseCustomTrackViaSize( true ); m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged ); } return 0; } int ROUTER_TOOL::onTrackViaSizeChanged( const TOOL_EVENT& aEvent ) { PNS::SIZES_SETTINGS sizes( m_router->Sizes() ); sizes.ImportCurrent( board()->GetDesignSettings() ); m_router->UpdateSizes( sizes ); return 0; }