diff --git a/pcbnew/tools/drawing_tool.cpp b/pcbnew/tools/drawing_tool.cpp index d65a646a72..215346c2c3 100644 --- a/pcbnew/tools/drawing_tool.cpp +++ b/pcbnew/tools/drawing_tool.cpp @@ -73,6 +73,7 @@ #include #include +const unsigned int DRAWING_TOOL::COORDS_PADDING = Millimeter2iu( 20 ); using SCOPED_DRAW_MODE = SCOPED_SET_RESET; @@ -1115,8 +1116,8 @@ int DRAWING_TOOL::DrawDimension( const TOOL_EVENT& aEvent ) } VECTOR2I cursorPos = evt->HasPosition() ? evt->Position() : m_controls->GetMousePosition(); + cursorPos = GetClampedCoords( grid.BestSnapAnchor( cursorPos, nullptr ), COORDS_PADDING ); - cursorPos = grid.BestSnapAnchor( cursorPos, nullptr ); m_controls->ForceCursorPosition( true, cursorPos ); if( evt->IsCancelInteractive() ) @@ -1844,7 +1845,9 @@ bool DRAWING_TOOL::drawShape( const std::string& aTool, PCB_SHAPE** aGraphic, grid.SetSnap( !evt->Modifier( MD_SHIFT ) ); grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() ); - cursorPos = grid.BestSnapAnchor( m_controls->GetMousePosition(), m_layer ); + cursorPos = GetClampedCoords( + grid.BestSnapAnchor( m_controls->GetMousePosition(), m_layer ), + COORDS_PADDING ); m_controls->ForceCursorPosition( true, cursorPos ); if( evt->IsCancelInteractive() ) @@ -2029,14 +2032,27 @@ bool DRAWING_TOOL::drawShape( const std::string& aTool, PCB_SHAPE** aGraphic, break; } - twoPointManager.SetEnd( cursorPos ); + twoPointManager.SetEnd( GetClampedCoords( cursorPos ) ); } else if( evt->IsMotion() ) { + VECTOR2I clampedCursorPos = cursorPos; + + if( shape == SHAPE_T::CIRCLE || shape == SHAPE_T::ARC ) + { + clampedCursorPos = getClampedRadiusEnd( twoPointManager.GetOrigin(), cursorPos ); + } + else + { + clampedCursorPos = + getClampedDifferenceEnd( twoPointManager.GetOrigin(), cursorPos ); + } + // 45 degree lines if( started && Is45Limited() ) { - const VECTOR2I lineVector( cursorPos - VECTOR2I( twoPointManager.GetOrigin() ) ); + const VECTOR2I lineVector( clampedCursorPos + - VECTOR2I( twoPointManager.GetOrigin() ) ); // get a restricted 45/H/V line from the last fixed point to the cursor VECTOR2I newEnd = GetVectorSnapped45( lineVector, ( shape == SHAPE_T::RECT ) ); @@ -2046,7 +2062,7 @@ bool DRAWING_TOOL::drawShape( const std::string& aTool, PCB_SHAPE** aGraphic, } else { - twoPointManager.SetEnd( cursorPos ); + twoPointManager.SetEnd( clampedCursorPos ); twoPointManager.SetAngleSnap( false ); } @@ -2191,7 +2207,8 @@ bool DRAWING_TOOL::drawArc( const std::string& aTool, PCB_SHAPE** aGraphic, grid.SetSnap( !evt->Modifier( MD_SHIFT ) ); grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() ); - VECTOR2I cursorPos = grid.BestSnapAnchor( m_controls->GetMousePosition(), graphic ); + VECTOR2I cursorPos = GetClampedCoords( + grid.BestSnapAnchor( m_controls->GetMousePosition(), graphic ), COORDS_PADDING ); m_controls->ForceCursorPosition( true, cursorPos ); if( evt->IsCancelInteractive() ) @@ -2511,7 +2528,7 @@ int DRAWING_TOOL::DrawZone( const TOOL_EVENT& aEvent ) grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() ); VECTOR2I cursorPos = evt->HasPosition() ? evt->Position() : m_controls->GetMousePosition(); - cursorPos = grid.BestSnapAnchor( cursorPos, layers ); + cursorPos = GetClampedCoords( grid.BestSnapAnchor( cursorPos, layers ), COORDS_PADDING ); m_controls->ForceCursorPosition( true, cursorPos ); diff --git a/pcbnew/tools/drawing_tool.h b/pcbnew/tools/drawing_tool.h index c75e507a4b..5594630b44 100644 --- a/pcbnew/tools/drawing_tool.h +++ b/pcbnew/tools/drawing_tool.h @@ -272,6 +272,65 @@ private: */ void constrainDimension( PCB_DIMENSION_BASE* aDim ); + /** + * Clamps the end vector to respect numeric limits of difference representation + * + * @param aOrigin - the origin vector. + * @param aEnd - the end vector. + * @return clamped end vector. + */ + VECTOR2I getClampedDifferenceEnd( const VECTOR2I& aOrigin, const VECTOR2I& aEnd ) + { + typedef std::numeric_limits coord_limits; + const int guardValue = 1; + + long maxDiff = coord_limits::max() - guardValue; + + long xDiff = long( aEnd.x ) - aOrigin.x; + long yDiff = long( aEnd.y ) - aOrigin.y; + + if( xDiff > maxDiff ) + xDiff = maxDiff; + if( yDiff > maxDiff ) + yDiff = maxDiff; + + if( xDiff < -maxDiff ) + xDiff = -maxDiff; + if( yDiff < -maxDiff ) + yDiff = -maxDiff; + + return aOrigin + VECTOR2I( int( xDiff ), int( yDiff ) ); + } + + /** + * Clamps the end vector to respect numeric limits of radius representation + * + * @param aOrigin - the origin vector. + * @param aEnd - the end vector. + * @return clamped end vector. + */ + VECTOR2I getClampedRadiusEnd( const VECTOR2I& aOrigin, const VECTOR2I& aEnd ) + { + typedef std::numeric_limits coord_limits; + const int guardValue = 10; + + long xDiff = long( aEnd.x ) - aOrigin.x; + long yDiff = long( aEnd.y ) - aOrigin.y; + + double maxRadius = coord_limits::max() / 2 - guardValue; + double radius = std::hypot( xDiff, yDiff ); + + if( radius > maxRadius ) + { + double scaleFactor = maxRadius / radius; + + xDiff = KiROUND( xDiff * scaleFactor ); + yDiff = KiROUND( yDiff * scaleFactor ); + } + + return aOrigin + VECTOR2I( int( xDiff ), int( yDiff ) ); + } + ///< Return the appropriate width for a segment depending on the settings. int getSegmentWidth( PCB_LAYER_ID aLayer ) const; @@ -287,6 +346,8 @@ private: TEXT_ATTRIBUTES m_textAttrs; static const unsigned int WIDTH_STEP; // Amount of width change for one -/+ key press + static const unsigned int COORDS_PADDING; // Padding from coordinates limits for this tool + friend class ZONE_CREATE_HELPER; // give internal access to helper classes }; diff --git a/pcbnew/tools/edit_tool.cpp b/pcbnew/tools/edit_tool.cpp index bf518de2a3..a78478d97e 100644 --- a/pcbnew/tools/edit_tool.cpp +++ b/pcbnew/tools/edit_tool.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -64,6 +65,7 @@ using namespace std::placeholders; #include #include +const unsigned int EDIT_TOOL::COORDS_PADDING = Millimeter2iu( 20 ); EDIT_TOOL::EDIT_TOOL() : PCB_TOOL_BASE( "pcbnew.InteractiveEdit" ), @@ -1061,40 +1063,64 @@ int EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent ) VECTOR2I refPt = selection.GetReferencePoint(); EDA_ANGLE rotateAngle = TOOL_EVT_UTILS::GetEventRotationAngle( *editFrame, aEvent ); - // When editing footprints, all items have the same parent - if( IsFootprintEditor() ) - m_commit->Modify( selection.Front() ); + // Calculate view bounding box + BOX2I viewBBox = selection.Front()->ViewBBox(); for( EDA_ITEM* item : selection ) - { - if( !item->IsNew() && !IsFootprintEditor() ) - { - m_commit->Modify( item ); + viewBBox.Merge( item->ViewBBox() ); - // If rotating a group, record position of all the descendants for undo - if( item->Type() == PCB_GROUP_T ) + // Check if the view bounding box will go out of bounds + VECTOR2D rotPos = viewBBox.GetPosition(); + VECTOR2D rotEnd = viewBBox.GetEnd(); + + RotatePoint( &rotPos.x, &rotPos.y, refPt.x, refPt.y, rotateAngle ); + RotatePoint( &rotEnd.x, &rotEnd.y, refPt.x, refPt.y, rotateAngle ); + + typedef std::numeric_limits coord_limits; + + long max = coord_limits::max() - COORDS_PADDING; + long min = -max; + + bool outOfBounds = rotPos.x < min || rotPos.x > max || rotPos.y < min || rotPos.y > max + || rotEnd.x < min || rotEnd.x > max || rotEnd.y < min || rotEnd.y > max; + + if( !outOfBounds ) + { + // When editing footprints, all items have the same parent + if( IsFootprintEditor() ) + m_commit->Modify( selection.Front() ); + + for( EDA_ITEM* item : selection ) + { + if( !item->IsNew() && !IsFootprintEditor() ) { - static_cast( item )->RunOnDescendants( [&]( BOARD_ITEM* bItem ) - { - m_commit->Modify( bItem ); - }); + m_commit->Modify( item ); + + // If rotating a group, record position of all the descendants for undo + if( item->Type() == PCB_GROUP_T ) + { + static_cast( item )->RunOnDescendants( [&]( BOARD_ITEM* bItem ) + { + m_commit->Modify( bItem ); + }); + } } + + static_cast( item )->Rotate( refPt, rotateAngle ); } - static_cast( item )->Rotate( refPt, rotateAngle ); + if( !m_dragging ) + m_commit->Push( _( "Rotate" ) ); + + if( is_hover && !m_dragging ) + m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true ); + + m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified ); + + if( m_dragging ) + m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false ); } - if( !m_dragging ) - m_commit->Push( _( "Rotate" ) ); - - if( is_hover && !m_dragging ) - m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true ); - - m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified ); - - if( m_dragging ) - m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false ); - // Restore the old reference so any mouse dragging that occurs doesn't make the selection jump // to this now invalid reference if( oldRefPt ) diff --git a/pcbnew/tools/edit_tool.h b/pcbnew/tools/edit_tool.h index 30a2be3a01..565742ee3b 100644 --- a/pcbnew/tools/edit_tool.h +++ b/pcbnew/tools/edit_tool.h @@ -182,6 +182,9 @@ private: int doMoveSelection( TOOL_EVENT aEvent, bool aPickReference = false ); + VECTOR2I getSafeMovement( const VECTOR2I& aMovement, const BOX2I& aSourceBBox, + const VECTOR2D& aBBoxOffset ); + bool pickReferencePoint( const wxString& aTooltip, const wxString& aSuccessMessage, const wxString& aCanceledMessage, VECTOR2I& aReferencePoint ); @@ -192,6 +195,8 @@ private: VECTOR2I m_cursor; // Last cursor position (so getModificationPoint() // can avoid changes of edit reference point). std::unique_ptr m_statusPopup; + + static const unsigned int COORDS_PADDING; // Padding from coordinates limits for this tool }; #endif diff --git a/pcbnew/tools/edit_tool_move_fct.cpp b/pcbnew/tools/edit_tool_move_fct.cpp index 6eec0f2e21..ccb1d8935c 100644 --- a/pcbnew/tools/edit_tool_move_fct.cpp +++ b/pcbnew/tools/edit_tool_move_fct.cpp @@ -294,6 +294,41 @@ int EDIT_TOOL::MoveWithReference( const TOOL_EVENT& aEvent ) } +VECTOR2I EDIT_TOOL::getSafeMovement( const VECTOR2I& aMovement, const BOX2I& aSourceBBox, + const VECTOR2D& aBBoxOffset ) +{ + typedef std::numeric_limits coord_limits; + + long max = coord_limits::max(); + long 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 ); +} + + // Note: aEvent MUST NOT be const&; the source will get de-allocated if we go into the picker's // event loop. int EDIT_TOOL::doMoveSelection( TOOL_EVENT aEvent, bool aPickReference ) @@ -397,6 +432,9 @@ int EDIT_TOOL::doMoveSelection( TOOL_EVENT aEvent, bool aPickReference ) 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* evt = &aEvent; VECTOR2I prevPos; @@ -467,12 +505,36 @@ int EDIT_TOOL::doMoveSelection( TOOL_EVENT aEvent, bool aPickReference ) 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 ); - movement = m_cursor - prevPos; prevPos = m_cursor; totalMovement += movement; + bboxMovement += movement; // Drag items to the current cursor position for( EDA_ITEM* item : sel_items ) @@ -637,6 +699,9 @@ int EDIT_TOOL::doMoveSelection( TOOL_EVENT aEvent, bool aPickReference ) originalPos = m_cursor; } + // Update variables for bounding box collision calculations + updateBBox = true; + controls->SetCursorPosition( m_cursor, false ); prevPos = m_cursor; @@ -675,6 +740,7 @@ int EDIT_TOOL::doMoveSelection( TOOL_EVENT aEvent, bool aPickReference ) || evt->IsAction( &PCB_ACTIONS::flip ) || evt->IsAction( &PCB_ACTIONS::mirror ) ) { + updateBBox = true; eatFirstMouseUp = false; evt->SetPassEvent(); } diff --git a/pcbnew/tools/pcb_point_editor.cpp b/pcbnew/tools/pcb_point_editor.cpp index 5670e49812..23bf893ca6 100644 --- a/pcbnew/tools/pcb_point_editor.cpp +++ b/pcbnew/tools/pcb_point_editor.cpp @@ -49,6 +49,7 @@ using namespace std::placeholders; #include #include +const unsigned int PCB_POINT_EDITOR::COORDS_PADDING = Millimeter2iu( 20 ); // Few constants to avoid using bare numbers for point indices enum SEG_POINTS @@ -543,7 +544,8 @@ int PCB_POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent ) } } - VECTOR2I pos = evt->Position(); + // Keep point inside of limits with some padding + VECTOR2I pos = GetClampedCoords( evt->Position(), COORDS_PADDING ); LSET snapLayers; switch( m_editedPoint->GetSnapConstraint() ) diff --git a/pcbnew/tools/pcb_point_editor.h b/pcbnew/tools/pcb_point_editor.h index a789edef0b..b76b76defa 100644 --- a/pcbnew/tools/pcb_point_editor.h +++ b/pcbnew/tools/pcb_point_editor.h @@ -177,6 +177,8 @@ private: // Alternative constraint, enabled while a modifier key is held std::shared_ptr> m_altConstraint; EDIT_POINT m_altConstrainer; + + static const unsigned int COORDS_PADDING; // Padding from coordinates limits for this tool }; #endif