/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2013-2017 CERN * Copyright (C) 2017-2022 KiCad Developers, see AUTHORS.txt for contributors. * @author Maciej Suminski * @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 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 #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 #include #include #include #include #include using namespace std::placeholders; #include "kicad_clipboard.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class DRC_TEST_PROVIDER_COURTYARD_CLEARANCE_ON_MOVE : public DRC_TEST_PROVIDER_CLEARANCE_BASE { public: DRC_TEST_PROVIDER_COURTYARD_CLEARANCE_ON_MOVE() : DRC_TEST_PROVIDER_CLEARANCE_BASE(), m_largestCourtyardClearance( 0 ) { m_isRuleDriven = false; } virtual ~DRC_TEST_PROVIDER_COURTYARD_CLEARANCE_ON_MOVE () { } void Init( BOARD* aBoard ); virtual bool Run() override; virtual const wxString GetName() const override { return wxT( "courtyard_clearance" ); } virtual const wxString GetDescription() const override { return wxT( "Tests footprints' courtyard clearance" ); } // The list of footprints having issues std::set m_FpInConflict; // The list of moved footprints std::vector m_FpInMove; private: void testCourtyardClearances(); private: int m_largestCourtyardClearance; }; void DRC_TEST_PROVIDER_COURTYARD_CLEARANCE_ON_MOVE::testCourtyardClearances() { for( FOOTPRINT* fpA: m_board->Footprints() ) { if( fpA->IsSelected() ) continue; const SHAPE_POLY_SET& frontA = fpA->GetCourtyard( F_CrtYd ); const SHAPE_POLY_SET& backA = fpA->GetCourtyard( B_CrtYd ); if( frontA.OutlineCount() == 0 && backA.OutlineCount() == 0 ) // No courtyards defined and no hole testing against other footprint's courtyards continue; BOX2I frontBBox = frontA.BBoxFromCaches(); BOX2I backBBox = backA.BBoxFromCaches(); frontBBox.Inflate( m_largestCourtyardClearance ); backBBox.Inflate( m_largestCourtyardClearance ); BOX2I fpABBox = fpA->GetBoundingBox(); for( FOOTPRINT* fpB : m_FpInMove ) { fpB->BuildCourtyardCaches(); BOX2I fpBBBox = fpB->GetBoundingBox(); const SHAPE_POLY_SET& frontB = fpB->GetCourtyard( F_CrtYd ); const SHAPE_POLY_SET& backB = fpB->GetCourtyard( B_CrtYd ); DRC_CONSTRAINT constraint; int clearance; int actual; VECTOR2I pos; if( frontA.OutlineCount() > 0 && frontB.OutlineCount() > 0 && frontBBox.Intersects( frontB.BBoxFromCaches() ) ) { // Currently, do not use DRC engine for calculation time reasons // constraint = m_drcEngine->EvalRules( COURTYARD_CLEARANCE_CONSTRAINT, fpA, fpB, B_Cu ); // constraint.GetValue().Min(); clearance = 0; if( frontA.Collide( &frontB, clearance, &actual, &pos ) ) { m_FpInConflict.insert( fpA ); m_FpInConflict.insert( fpB ); } } if( backA.OutlineCount() > 0 && backB.OutlineCount() > 0 && backBBox.Intersects( backB.BBoxFromCaches() ) ) { // Currently, do not use DRC engine for calculation time reasons // constraint = m_drcEngine->EvalRules( COURTYARD_CLEARANCE_CONSTRAINT, fpA, fpB, B_Cu ); // constraint.GetValue().Min(); clearance = 0; if( backA.Collide( &backB, clearance, &actual, &pos ) ) { m_FpInConflict.insert( fpA ); m_FpInConflict.insert( fpB ); } } // Now test if a pad hole of some other footprint is inside the courtyard area // of the moved footprint auto testPadAgainstCourtyards = [&]( const PAD* pad, FOOTPRINT* footprint ) -> bool { if( pad->HasHole() ) { std::shared_ptr hole = pad->GetEffectiveHoleShape(); const SHAPE_POLY_SET& front = footprint->GetCourtyard( F_CrtYd ); const SHAPE_POLY_SET& back = footprint->GetCourtyard( B_CrtYd ); if( front.OutlineCount() > 0 && front.Collide( hole.get(), 0 ) ) return true; else if( back.OutlineCount() > 0 && back.Collide( hole.get(), 0 ) ) return true; } return false; }; bool skipNextCmp = false; if( ( frontA.OutlineCount() > 0 && frontA.BBoxFromCaches().Intersects( fpBBBox ) ) || ( backA.OutlineCount() > 0 && backA.BBoxFromCaches().Intersects( fpBBBox ) ) ) { for( const PAD* padB : fpB->Pads() ) { if( testPadAgainstCourtyards( padB, fpA ) ) { m_FpInConflict.insert( fpA ); m_FpInConflict.insert( fpB ); skipNextCmp = true; break; } } } if( skipNextCmp ) continue; // fpA and fpB are already in list if( ( frontB.OutlineCount() > 0 && frontB.BBoxFromCaches().Intersects( fpABBox ) ) || ( backB.OutlineCount() > 0 && backB.BBoxFromCaches().Intersects( fpABBox ) ) ) { for( const PAD* padA : fpA->Pads() ) { if( testPadAgainstCourtyards( padA, fpB ) ) { m_FpInConflict.insert( fpA ); m_FpInConflict.insert( fpB ); break; } } } } } } void DRC_TEST_PROVIDER_COURTYARD_CLEARANCE_ON_MOVE::Init( BOARD* aBoard ) { m_board = aBoard; // Update courtyard data and clear the COURTYARD_CONFLICT flag for( FOOTPRINT* fp: m_board->Footprints() ) { fp->ClearFlags( COURTYARD_CONFLICT ); fp->BuildCourtyardCaches(); } } bool DRC_TEST_PROVIDER_COURTYARD_CLEARANCE_ON_MOVE::Run() { m_FpInConflict.clear(); m_largestCourtyardClearance = 0; DRC_CONSTRAINT constraint; if( m_drcEngine->QueryWorstConstraint( COURTYARD_CLEARANCE_CONSTRAINT, constraint ) ) m_largestCourtyardClearance = constraint.GetValue().Min(); testCourtyardClearances(); return true; } int EDIT_TOOL::Swap( const TOOL_EVENT& aEvent ) { if( isRouterActive() || m_dragging ) { wxBell(); return 0; } PCB_SELECTION& selection = m_selectionTool->RequestSelection( []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool ) { sTool->FilterCollectorForMarkers( aCollector ); sTool->FilterCollectorForHierarchy( aCollector, true ); sTool->FilterCollectorForFreePads( aCollector ); // Iterate from the back so we don't have to worry about removals. for( int i = aCollector.GetCount() - 1; i >= 0; --i ) { BOARD_ITEM* item = aCollector[i]; switch( item->Type() ) { case PCB_TRACE_T: aCollector.Remove( item ); break; default: break; } } }, true /* prompt user regarding locked items */ ); if( selection.Size() < 2 ) return 0; std::vector sorted = selection.GetItemsSortedBySelectionOrder(); // When editing footprints, all items have the same parent if( IsFootprintEditor() ) { m_commit->Modify( selection.Front() ); } else { // Save items, so changes can be undone for( EDA_ITEM* item : selection ) { // Don't double move footprint pads, fields, etc. // // For PCB_GROUP_T, the parent is the board. if( item->GetParent() && item->GetParent()->IsSelected() ) continue; m_commit->Modify( item ); // If moving a group, record position of all the descendants for undo if( item->Type() == PCB_GROUP_T ) { PCB_GROUP* group = static_cast( item ); group->RunOnDescendants( [&]( BOARD_ITEM* bItem ) { m_commit->Modify( bItem ); }); } } } for( size_t i = 0; i < sorted.size() - 1; i++ ) { BOARD_ITEM* a = static_cast( sorted[i] ); BOARD_ITEM* b = static_cast( sorted[( i + 1 ) % sorted.size()] ); // Swap X,Y position VECTOR2I aPos = a->GetPosition(), bPos = b->GetPosition(); std::swap( aPos, bPos ); a->SetPosition( aPos ); b->SetPosition( bPos ); // Pads need special handling to keep their offset from their parent if( a->Type() == PCB_PAD_T ) static_cast( a )->SetLocalCoord(); if( b->Type() == PCB_PAD_T ) static_cast( b )->SetLocalCoord(); // Handle footprints specially. They can be flipped to the back of the board which // requires a special transformation. if( a->Type() == PCB_FOOTPRINT_T && b->Type() == PCB_FOOTPRINT_T ) { FOOTPRINT* aFP = static_cast( a ); FOOTPRINT* bFP = static_cast( b ); // Flip both if needed if( aFP->IsFlipped() != bFP->IsFlipped() ) { aFP->Flip( aPos, false ); bFP->Flip( bPos, false ); } // Set orientation EDA_ANGLE aAngle = aFP->GetOrientation(), bAngle = bFP->GetOrientation(); std::swap( aAngle, bAngle ); aFP->SetOrientation( aAngle ); bFP->SetOrientation( bAngle ); } // We can also do a layer swap safely for two objects of the same type, // except groups which don't support layer swaps. else if( a->Type() == b->Type() && a->Type() != PCB_GROUP_T ) { // Swap layers PCB_LAYER_ID aLayer = a->GetLayer(), bLayer = b->GetLayer(); std::swap( aLayer, bLayer ); a->SetLayer( aLayer ); b->SetLayer( bLayer ); } } m_commit->Push( _( "Swap" ) ); m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified ); return 0; } int EDIT_TOOL::PackAndMoveFootprints( const TOOL_EVENT& aEvent ) { PCB_BASE_EDIT_FRAME* editFrame = getEditFrame(); PCB_SELECTION& selection = m_selectionTool->RequestSelection( []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool ) { // Iterate from the back so we don't have to worry about removals. for( int i = aCollector.GetCount() - 1; i >= 0; --i ) { BOARD_ITEM* item = aCollector[i]; if( !dynamic_cast( item ) ) aCollector.Remove( item ); } }, true /* prompt user regarding locked items */ ); std::vector footprintsToPack; for( EDA_ITEM* item : selection ) footprintsToPack.push_back( static_cast( item ) ); if( footprintsToPack.empty() ) return 0; BOARD_COMMIT commit( editFrame ); BOX2I footprintsBbox; for( FOOTPRINT* item : footprintsToPack ) { commit.Modify( item ); footprintsBbox.Merge( item->GetBoundingBox( false, false ) ); } SpreadFootprints( &footprintsToPack, footprintsBbox.Normalize().GetOrigin(), false ); commit.Push( _( "Pack footprints" ) ); return doMoveSelection( aEvent ); } int EDIT_TOOL::Move( const TOOL_EVENT& aEvent ) { if( isRouterActive() ) { wxBell(); return 0; } return doMoveSelection( aEvent ); } int EDIT_TOOL::MoveIndividually( const TOOL_EVENT& aEvent ) { PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool(); if( isRouterActive() ) { wxBell(); return 0; } EDA_ITEMS sortedItems = selTool->GetSelection().GetItemsSortedBySelectionOrder(); if( sortedItems.size() == 0 ) return 0; for( EDA_ITEM* item : sortedItems ) { selTool->ClearSelection(); selTool->AddItemToSel( item ); doMoveSelection( aEvent ); } selTool->AddItemsToSel( &sortedItems ); return 0; } int EDIT_TOOL::MoveWithReference( const TOOL_EVENT& aEvent ) { if( isRouterActive() ) { wxBell(); return 0; } return doMoveSelection( aEvent, true ); } VECTOR2I EDIT_TOOL::getSafeMovement( const VECTOR2I& aMovement, const BOX2I& aSourceBBox, const VECTOR2D& aBBoxOffset ) { typedef std::numeric_limits coord_limits; int max = coord_limits::max(); int min = -max; double left = aBBoxOffset.x + aSourceBBox.GetPosition().x; double top = aBBoxOffset.y + aSourceBBox.GetPosition().y; double right = left + aSourceBBox.GetSize().x; double bottom = top + aSourceBBox.GetSize().y; // Do not restrict movement if bounding box is already out of bounds if( left < min || top < min || right > max || bottom > max ) return aMovement; // Constrain moving bounding box to coordinates limits VECTOR2D tryMovement( aMovement ); VECTOR2D clampedBBoxOrigin = GetClampedCoords( VECTOR2D( aSourceBBox.GetPosition() ) + aBBoxOffset + tryMovement, COORDS_PADDING ); tryMovement = clampedBBoxOrigin - aBBoxOffset - aSourceBBox.GetPosition(); VECTOR2D clampedBBoxEnd = GetClampedCoords( VECTOR2D( aSourceBBox.GetEnd() ) + aBBoxOffset + tryMovement, COORDS_PADDING ); tryMovement = clampedBBoxEnd - aBBoxOffset - aSourceBBox.GetEnd(); return GetClampedCoords( tryMovement ); } int EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, bool aPickReference ) { PCB_BASE_EDIT_FRAME* editFrame = getEditFrame(); BOARD* board = editFrame->GetBoard(); KIGFX::VIEW_CONTROLS* controls = getViewControls(); VECTOR2I originalCursorPos = controls->GetCursorPosition(); // Be sure that there is at least one item that we can modify. If nothing was selected before, // try looking for the stuff under mouse cursor (i.e. KiCad old-style hover selection) PCB_SELECTION& selection = m_selectionTool->RequestSelection( []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool ) { sTool->FilterCollectorForMarkers( aCollector ); sTool->FilterCollectorForHierarchy( aCollector, true ); }, // Prompt user regarding locked items if in board editor and in free-pad-mode (if // we're not in free-pad mode we delay this until the second RequestSelection()). editFrame->GetPcbNewSettings()->m_AllowFreePads && !m_isFootprintEditor ); if( m_dragging || selection.Empty() ) return 0; LSET item_layers = selection.GetSelectionLayers(); bool is_hover = selection.IsHover(); // N.B. This must be saved before the second call // to RequestSelection() below VECTOR2I pickedReferencePoint; // Now filter out pads if not in free pads mode. We cannot do this in the first // RequestSelection() as we need the item_layers when a pad is the selection front. if( !m_isFootprintEditor && !editFrame->GetPcbNewSettings()->m_AllowFreePads ) { selection = m_selectionTool->RequestSelection( []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool ) { sTool->FilterCollectorForMarkers( aCollector ); sTool->FilterCollectorForHierarchy( aCollector, true ); sTool->FilterCollectorForFreePads( aCollector ); }, true /* prompt user regarding locked items */ ); } if( selection.Empty() ) return 0; editFrame->PushTool( aEvent ); Activate(); // Must be done after Activate() so that it gets set into the correct context controls->ShowCursor( true ); controls->SetAutoPan( true ); controls->ForceCursorPosition( false ); if( aPickReference && !pickReferencePoint( _( "Select reference point for move..." ), "", "", pickedReferencePoint ) ) { if( is_hover ) m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true ); editFrame->PopTool( aEvent ); return 0; } auto displayConstraintsMessage = [editFrame]( bool constrained ) { editFrame->DisplayConstraintsMsg( constrained ? _( "Constrain to H, V, 45" ) : wxT( "" ) ); }; std::vector sel_items; // All the items operated on by the move below std::vector orig_items; // All the original items in the selection std::vector lastFpInConflict; // last footprints with courtyard overlapping for( EDA_ITEM* item : selection ) { BOARD_ITEM* boardItem = dynamic_cast( item ); FOOTPRINT* footprint = dynamic_cast( item ); if( boardItem ) { if( !is_hover ) orig_items.push_back( boardItem ); sel_items.push_back( boardItem ); } if( footprint ) { for( PAD* pad : footprint->Pads() ) sel_items.push_back( pad ); // Clear this flag here; it will be set by the netlist updater if the footprint is new // so that it was skipped in the initial connectivity update in OnNetlistChanged footprint->SetAttributes( footprint->GetAttributes() & ~FP_JUST_ADDED ); } } bool restore_state = false; VECTOR2I originalPos; VECTOR2I totalMovement; VECTOR2D bboxMovement; BOX2I originalBBox; bool updateBBox = true; PCB_GRID_HELPER grid( m_toolMgr, editFrame->GetMagneticItemsSettings() ); TOOL_EVENT copy = aEvent; TOOL_EVENT* evt = © VECTOR2I prevPos; bool hv45Mode = false; bool eatFirstMouseUp = true; bool hasRedrawn3D = false; bool allowRedraw3D = editFrame->GetPcbNewSettings()->m_Display.m_Live3DRefresh; // Courtyard conflicts will be tested only if the LAYER_CONFLICTS_SHADOW gal layer is visible bool showCourtyardConflicts = !m_isFootprintEditor && board->IsElementVisible( LAYER_CONFLICTS_SHADOW ); displayConstraintsMessage( hv45Mode ); // Used to test courtyard overlaps DRC_TEST_PROVIDER_COURTYARD_CLEARANCE_ON_MOVE drc_on_move; if( showCourtyardConflicts ) { drc_on_move.Init( board ); drc_on_move.SetDRCEngine( m_toolMgr->GetTool()->GetDRCEngine().get() ); } // Prime the pump m_toolMgr->RunAction( ACTIONS::refreshPreview ); // Main loop: keep receiving events do { VECTOR2I movement; editFrame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING ); grid.SetSnap( !evt->Modifier( MD_SHIFT ) ); grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() ); if( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) eatFirstMouseUp = false; if( evt->IsAction( &PCB_ACTIONS::move ) || evt->IsMotion() || evt->IsDrag( BUT_LEFT ) || evt->IsAction( &ACTIONS::refreshPreview ) || evt->IsAction( &PCB_ACTIONS::moveWithReference ) || evt->IsAction( &PCB_ACTIONS::moveIndividually ) ) { if( m_dragging && evt->Category() == TC_MOUSE ) { bool redraw3D = false; VECTOR2I mousePos( controls->GetMousePosition() ); m_cursor = grid.BestSnapAnchor( mousePos, item_layers, sel_items ); if( controls->GetSettings().m_lastKeyboardCursorPositionValid ) { long action = controls->GetSettings().m_lastKeyboardCursorCommand; // The arrow keys are by definition SINGLE AXIS. Do not allow the other // axis to be snapped to the grid. if( action == ACTIONS::CURSOR_LEFT || action == ACTIONS::CURSOR_RIGHT ) m_cursor.y = prevPos.y; else if( action == ACTIONS::CURSOR_UP || action == ACTIONS::CURSOR_DOWN ) m_cursor.x = prevPos.x; } if( !selection.HasReferencePoint() ) originalPos = m_cursor; if( hv45Mode ) { VECTOR2I moveVector = m_cursor - originalPos; m_cursor = originalPos + GetVectorSnapped45( moveVector ); } if( updateBBox ) { originalBBox = BOX2I(); bboxMovement = VECTOR2D(); for( EDA_ITEM* item : sel_items ) { BOX2I viewBBOX = item->ViewBBox(); if( originalBBox.GetWidth() == 0 && originalBBox.GetHeight() == 0 ) originalBBox = viewBBOX; else originalBBox.Merge( viewBBOX ); } updateBBox = false; } // Constrain selection bounding box to coordinates limits movement = getSafeMovement( m_cursor - prevPos, originalBBox, bboxMovement ); // Apply constrained movement m_cursor = prevPos + movement; controls->ForceCursorPosition( true, m_cursor ); selection.SetReferencePoint( m_cursor ); prevPos = m_cursor; totalMovement += movement; bboxMovement += movement; // Drag items to the current cursor position for( EDA_ITEM* item : sel_items ) { // Don't double move footprint pads, fields, etc. // // For PCB_GROUP_T, we make sure the selection includes only the top level // group and not its descendants. if( !item->GetParent() || !item->GetParent()->IsSelected() ) static_cast( item )->Move( movement ); if( !redraw3D && item->Type() == PCB_FOOTPRINT_T ) redraw3D = true; } if( redraw3D && allowRedraw3D ) { editFrame->Update3DView( false, true ); hasRedrawn3D = true; } if( showCourtyardConflicts && drc_on_move.m_FpInMove.size() ) { drc_on_move.Run(); bool need_redraw = false; // will be set to true if a COURTYARD_CONFLICT // has changed // Ensure the "old" conflicts are cleared for( FOOTPRINT* fp: lastFpInConflict ) { fp->ClearFlags( COURTYARD_CONFLICT ); m_toolMgr->GetView()->Update( fp ); need_redraw = true; } lastFpInConflict.clear(); for( FOOTPRINT* fp: drc_on_move.m_FpInConflict ) { if( !fp->HasFlag( COURTYARD_CONFLICT ) ) { fp->SetFlags( COURTYARD_CONFLICT ); m_toolMgr->GetView()->Update( fp ); need_redraw = true; } lastFpInConflict.push_back( fp ); } if( need_redraw ) m_toolMgr->GetView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY ); } m_toolMgr->PostEvent( EVENTS::SelectedItemsMoved ); } else if( !m_dragging && !evt->IsAction( &ACTIONS::refreshPreview ) ) { // Prepare to start dragging if( !( evt->IsAction( &PCB_ACTIONS::move ) || evt->IsAction( &PCB_ACTIONS::moveWithReference ) || evt->IsAction( &PCB_ACTIONS::moveIndividually ) ) && ( editFrame->GetPcbNewSettings()->m_TrackDragAction != TRACK_DRAG_ACTION::MOVE ) ) { if( invokeInlineRouter( PNS::DM_ANY ) ) break; } editFrame->HideSolderMask(); m_dragging = true; // When editing footprints, all items have the same parent if( IsFootprintEditor() ) { m_commit->Modify( selection.Front() ); } else { // Save items, so changes can be undone for( EDA_ITEM* item : selection ) { // Don't double move footprint pads, fields, etc. // // For PCB_GROUP_T, the parent is the board. if( item->GetParent() && item->GetParent()->IsSelected() ) continue; m_commit->Modify( item ); // If moving a group, record position of all the descendants for undo if( item->Type() == PCB_GROUP_T ) { PCB_GROUP* group = static_cast( item ); group->RunOnDescendants( [&]( BOARD_ITEM* bItem ) { m_commit->Modify( bItem ); }); } } } editFrame->UndoRedoBlock( true ); m_cursor = controls->GetCursorPosition(); if( selection.HasReferencePoint() ) { // start moving with the reference point attached to the cursor grid.SetAuxAxes( false ); if( hv45Mode ) { VECTOR2I moveVector = m_cursor - originalPos; m_cursor = originalPos + GetVectorSnapped45( moveVector ); } movement = m_cursor - selection.GetReferencePoint(); // Drag items to the current cursor position for( EDA_ITEM* item : selection ) { // Don't double move footprint pads, fields, etc. if( item->GetParent() && item->GetParent()->IsSelected() ) continue; static_cast( item )->Move( movement ); } selection.SetReferencePoint( m_cursor ); } else { std::vector items; for( EDA_ITEM* item : selection ) { items.push_back( static_cast( item ) ); if( item->Type() == PCB_FOOTPRINT_T ) drc_on_move.m_FpInMove.push_back( static_cast( item ) ); } m_cursor = grid.BestDragOrigin( originalCursorPos, items ); // Set the current cursor position to the first dragged item origin, so the // movement vector could be computed later if( aPickReference ) { selection.SetReferencePoint( pickedReferencePoint ); controls->ForceCursorPosition( true, pickedReferencePoint ); m_cursor = pickedReferencePoint; } else { // Check if user wants to warp the mouse to origin of moved object if( !editFrame->GetMoveWarpsCursor() ) m_cursor = originalCursorPos; // No, so use original mouse pos instead selection.SetReferencePoint( m_cursor ); grid.SetAuxAxes( true, m_cursor ); } originalPos = m_cursor; } // Update variables for bounding box collision calculations updateBBox = true; controls->SetCursorPosition( m_cursor, false ); prevPos = m_cursor; controls->SetAutoPan( true ); m_toolMgr->PostEvent( EVENTS::SelectedItemsModified ); } m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false, new VECTOR2I( movement ) ); } else if( evt->IsCancelInteractive() || evt->IsActivate() ) { if( m_dragging && evt->IsCancelInteractive() ) evt->SetPassEvent( false ); restore_state = true; // Canceling the tool means that items have to be restored break; // Finish } else if( evt->IsAction( &ACTIONS::undo ) ) { restore_state = true; // Perform undo locally break; // Finish } else if( evt->IsAction( &ACTIONS::doDelete ) || evt->IsAction( &ACTIONS::cut ) ) { // Dispatch TOOL_ACTIONs evt->SetPassEvent(); break; // finish -- there is no further processing for removed items } else if( evt->IsAction( &ACTIONS::duplicate ) ) { evt->SetPassEvent(); break; // finish -- Duplicate tool will start a new Move with the dup'ed items } else if( evt->IsAction( &PCB_ACTIONS::rotateCw ) || evt->IsAction( &PCB_ACTIONS::rotateCcw ) || evt->IsAction( &PCB_ACTIONS::flip ) || evt->IsAction( &PCB_ACTIONS::mirrorH ) || evt->IsAction( &PCB_ACTIONS::mirrorV ) ) { updateBBox = true; eatFirstMouseUp = false; evt->SetPassEvent(); } else if( evt->IsAction( &PCB_ACTIONS::moveExact ) ) { // Reset positions so the Move Exactly is from the start. for( EDA_ITEM* item : selection ) { BOARD_ITEM* i = static_cast( item ); i->Move( -totalMovement ); } break; // finish -- we moved exactly, so we are finished } else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) ) { // Eat mouse-up/-click events that leaked through from the lock dialog if( eatFirstMouseUp && evt->Parameter() != ACTIONS::CURSOR_CLICK ) { eatFirstMouseUp = false; continue; } break; // finish } else if( evt->IsAction( &PCB_ACTIONS::toggleHV45Mode ) ) { hv45Mode = !hv45Mode; displayConstraintsMessage( hv45Mode ); evt->SetPassEvent( false ); } else { evt->SetPassEvent(); } } while( ( evt = Wait() ) ); // Assignment (instead of equality test) is intentional // Clear temporary COURTYARD_CONFLICT flag and ensure the conflict shadow is cleared for( FOOTPRINT* fp: lastFpInConflict ) { m_toolMgr->GetView()->Update( fp ); fp->ClearFlags( COURTYARD_CONFLICT ); } controls->ForceCursorPosition( false ); controls->ShowCursor( false ); controls->SetAutoPan( false ); m_dragging = false; editFrame->UndoRedoBlock( false ); m_toolMgr->GetTool()->UpdateStatusBar(); if( hasRedrawn3D && restore_state ) editFrame->Update3DView( false, true ); // Discard reference point when selection is "dropped" onto the board selection.ClearReferencePoint(); // Unselect all items to clear selection flags and then re-select the originally selected // items. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true ); m_toolMgr->RunAction( PCB_ACTIONS::selectItems, true, &orig_items ); // TODO: there's an encapsulation leak here: this commit often has more than just the move // in it; for instance it might have a paste, append board, etc. as well. if( restore_state ) m_commit->Revert(); else m_commit->Push( _( "Drag" ) ); // Remove the dynamic ratsnest from the screen m_toolMgr->RunAction( PCB_ACTIONS::hideLocalRatsnest, true ); editFrame->PopTool( aEvent ); editFrame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW ); return restore_state ? -1 : 0; }