diff --git a/pcbnew/tools/edit_tool.cpp b/pcbnew/tools/edit_tool.cpp index ce6ccd91d6..e031a8ca3b 100644 --- a/pcbnew/tools/edit_tool.cpp +++ b/pcbnew/tools/edit_tool.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -180,6 +181,10 @@ bool EDIT_TOOL::Init() PCB_ARC_T, PCB_VIA_T }; + static std::vector filletTypes = { PCB_SHAPE_LOCATE_POLY_T, + PCB_SHAPE_LOCATE_RECT_T, + PCB_SHAPE_LOCATE_SEGMENT_T }; + // Add context menu entries that are displayed when selection tool is active CONDITIONAL_MENU& menu = m_selectionTool->GetToolMenu().GetMenu(); @@ -198,6 +203,7 @@ bool EDIT_TOOL::Init() && SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::DraggableItems ) && !SELECTION_CONDITIONS::OnlyTypes( { PCB_FOOTPRINT_T } ) ); menu.AddItem( PCB_ACTIONS::filletTracks, SELECTION_CONDITIONS::OnlyTypes( trackTypes ) ); + menu.AddItem( PCB_ACTIONS::filletLines, SELECTION_CONDITIONS::OnlyTypes( filletTypes ) ); menu.AddItem( PCB_ACTIONS::rotateCcw, SELECTION_CONDITIONS::NotEmpty ); menu.AddItem( PCB_ACTIONS::rotateCw, SELECTION_CONDITIONS::NotEmpty ); menu.AddItem( PCB_ACTIONS::flip, SELECTION_CONDITIONS::NotEmpty ); @@ -1026,6 +1032,280 @@ int EDIT_TOOL::FilletTracks( const TOOL_EVENT& aEvent ) } +int EDIT_TOOL::FilletLines( const TOOL_EVENT& aEvent ) +{ + // Store last used fillet radius to allow pressing "enter" if repeat fillet is required + static long long filletRadiusIU = 0; + + PCB_SELECTION& selection = m_selectionTool->RequestSelection( + []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool ) + { + + std::vector pts; + + // 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]; + + // We've converted the polygon and rectangle to segments, so drop everything + // that isn't a segment at this point + if( !item->IsType( { PCB_SHAPE_LOCATE_SEGMENT_T, + PCB_SHAPE_LOCATE_POLY_T, + PCB_SHAPE_LOCATE_RECT_T } ) ) + { + aCollector.Remove( item ); + } + } + }, + true /* prompt user regarding locked items */ ); + + std::set lines_to_add; + std::vector items_to_remove; + + for( EDA_ITEM* item : selection ) + { + std::vector pts; + PCB_SHAPE *graphic = static_cast( item ); + + if( graphic->GetShape() == SHAPE_T::RECT ) + { + items_to_remove.push_back( graphic ); + VECTOR2I start( graphic->GetStart() ); + VECTOR2I end( graphic->GetEnd() ); + pts.emplace_back( start ); + pts.emplace_back( VECTOR2I( end.x, start.y ) ); + pts.emplace_back( end ); + pts.emplace_back( VECTOR2I( start.x, end.y ) ); + } + + if( graphic->GetShape() == SHAPE_T::POLY ) + { + items_to_remove.push_back( graphic ); + + for( int jj = 0; jj < graphic->GetPolyShape().VertexCount(); ++jj ) + pts.emplace_back( graphic->GetPolyShape().CVertex( jj ) ); + } + + for( size_t jj = 1; jj < pts.size(); ++jj ) + { + PCB_SHAPE *line; + + if( m_isFootprintEditor ) + line = new FP_SHAPE( static_cast( frame()->GetModel() ), SHAPE_T::SEGMENT ); + else + line = new PCB_SHAPE( frame()->GetModel(), SHAPE_T::SEGMENT ); + + line->SetStart( pts[jj - 1] ); + line->SetEnd( pts[jj] ); + lines_to_add.insert( line ); + } + + if( pts.size() > 1 ) + { + PCB_SHAPE *line; + + if( m_isFootprintEditor ) + line = new FP_SHAPE( static_cast( frame()->GetModel() ), SHAPE_T::SEGMENT ); + else + line = new PCB_SHAPE( frame()->GetModel(), SHAPE_T::SEGMENT ); + + line->SetStart( pts.back() ); + line->SetEnd( pts.front() ); + lines_to_add.insert( line ); + } + } + + for( PCB_SHAPE* item : lines_to_add ) + selection.Add( item ); + + for( PCB_SHAPE* item : items_to_remove ) + selection.Remove( item ); + + if( selection.CountType( PCB_SHAPE_LOCATE_SEGMENT_T ) < 2 ) + { + frame()->ShowInfoBarMsg( _( "A shape with least two lines must be selected." ) ); + return 0; + } + + WX_UNIT_ENTRY_DIALOG dia( frame(), _( "Enter fillet radius:" ), _( "Fillet Lines" ), + filletRadiusIU ); + + if( dia.ShowModal() == wxID_CANCEL ) + return 0; + + filletRadiusIU = dia.GetValue(); + + if( filletRadiusIU == 0 ) + { + frame()->ShowInfoBarMsg( _( "A radius of zero was entered.\n" + "The fillet operation was not performed." ) ); + return 0; + } + + bool operationPerformedOnAtLeastOne = false; + bool didOneAttemptFail = false; + std::vector itemsToAddToSelection; + + // Only modify one parent in FP editor + if( m_isFootprintEditor ) + m_commit->Modify( selection.Front() ); + + alg::for_all_pairs( selection.begin(), selection.end(), [&]( EDA_ITEM* a, EDA_ITEM* b ) + { + PCB_SHAPE* line_a = static_cast( a ); + PCB_SHAPE* line_b = static_cast( b ); + + if( line_a->GetLength() == 0.0 || line_b->GetLength() == 0 ) + return; + + SEG seg_a( line_a->GetStart(), line_a->GetEnd() ); + SEG seg_b( line_b->GetStart(), line_b->GetEnd() ); + VECTOR2I* a_pt; + VECTOR2I* b_pt; + + if (seg_a.A == seg_b.A) + { + a_pt = &seg_a.A; + b_pt = &seg_b.A; + } + else if (seg_a.A == seg_b.B) + { + a_pt = &seg_a.A; + b_pt = &seg_b.B; + } + else if (seg_a.B == seg_b.A) + { + a_pt = &seg_a.B; + b_pt = &seg_b.A; + } + else if (seg_a.B == seg_b.B) + { + a_pt = &seg_a.B; + b_pt = &seg_b.B; + } + else + return; + + + SHAPE_ARC sArc( seg_a, seg_b, filletRadiusIU ); + VECTOR2I t1newPoint, t2newPoint; + + auto setIfPointOnSeg = + []( VECTOR2I& aPointToSet, SEG aSegment, VECTOR2I aVecToTest ) + { + VECTOR2I segToVec = aSegment.NearestPoint( aVecToTest ) - aVecToTest; + + // Find out if we are on the segment (minimum precision) + if( segToVec.EuclideanNorm() < SHAPE_ARC::MIN_PRECISION_IU ) + { + aPointToSet.x = aVecToTest.x; + aPointToSet.y = aVecToTest.y; + return true; + } + + return false; + }; + + //Do not draw a fillet if the end points of the arc are not within the track segments + if( !setIfPointOnSeg( t1newPoint, seg_a, sArc.GetP0() ) + && !setIfPointOnSeg( t2newPoint, seg_b, sArc.GetP0() ) ) + { + didOneAttemptFail = true; + return; + } + + if( !setIfPointOnSeg( t1newPoint, seg_a, sArc.GetP1() ) + && !setIfPointOnSeg( t2newPoint, seg_b, sArc.GetP1() ) ) + { + didOneAttemptFail = true; + return; + } + + PCB_SHAPE* tArc; + + if( m_isFootprintEditor ) + tArc = new FP_SHAPE( static_cast( frame()->GetModel() ), SHAPE_T::ARC ); + else + tArc = new PCB_SHAPE( frame()->GetBoard(), SHAPE_T::ARC ); + + tArc->SetArcGeometry( sArc.GetP0(), sArc.GetArcMid(), sArc.GetP1() ); + tArc->SetWidth( line_a->GetWidth() ); + tArc->SetLayer( line_a->GetLayer() ); + tArc->SetLocked( line_a->IsLocked() ); + + if( lines_to_add.count( line_a ) ) + { + lines_to_add.erase( line_a ); + itemsToAddToSelection.push_back( line_a ); + } + else if( !m_isFootprintEditor ) + { + m_commit->Modify( line_a ); + } + + if( lines_to_add.count( line_b ) ) + { + lines_to_add.erase( line_b ); + itemsToAddToSelection.push_back( line_b ); + } + else if( !m_isFootprintEditor ) + { + m_commit->Modify( line_b ); + } + + itemsToAddToSelection.push_back( tArc ); + *a_pt = t1newPoint; + *b_pt = t2newPoint; + line_a->SetStart( seg_a.A ); + line_a->SetEnd( seg_a.B ); + line_b->SetStart( seg_b.A ); + line_b->SetEnd( seg_b.B ); + + if( m_isFootprintEditor ) + { + static_cast( line_a )->SetLocalCoord(); + static_cast( line_b )->SetLocalCoord(); + static_cast( tArc )->SetLocalCoord(); + } + + operationPerformedOnAtLeastOne = true; + + } ); + + for( auto item : items_to_remove ) + { + m_commit->Remove( item ); + m_selectionTool->RemoveItemFromSel( item, true ); + } + + //select the newly created arcs + for( BOARD_ITEM* item : itemsToAddToSelection ) + { + m_commit->Add( item ); + m_selectionTool->AddItemToSel( item, true ); + } + + if( !items_to_remove.empty() ) + m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent ); + + if( !itemsToAddToSelection.empty() ) + m_toolMgr->ProcessEvent( EVENTS::SelectedEvent ); + + // Notify other tools of the changes + m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified ); + + m_commit->Push( _( "Fillet Lines" ) ); + + if( !operationPerformedOnAtLeastOne ) + frame()->ShowInfoBarMsg( _( "Unable to fillet the selected lines." ) ); + else if( didOneAttemptFail ) + frame()->ShowInfoBarMsg( _( "Some of the lines could not be filleted." ) ); + + return 0; +} + + int EDIT_TOOL::Properties( const TOOL_EVENT& aEvent ) { PCB_BASE_EDIT_FRAME* editFrame = getEditFrame(); @@ -2280,6 +2560,7 @@ void EDIT_TOOL::setTransitions() Go( &EDIT_TOOL::PackAndMoveFootprints, PCB_ACTIONS::packAndMoveFootprints.MakeEvent() ); Go( &EDIT_TOOL::ChangeTrackWidth, PCB_ACTIONS::changeTrackWidth.MakeEvent() ); Go( &EDIT_TOOL::FilletTracks, PCB_ACTIONS::filletTracks.MakeEvent() ); + Go( &EDIT_TOOL::FilletLines, PCB_ACTIONS::filletLines.MakeEvent() ); Go( &EDIT_TOOL::copyToClipboard, ACTIONS::copy.MakeEvent() ); Go( &EDIT_TOOL::copyToClipboard, PCB_ACTIONS::copyWithReference.MakeEvent() ); diff --git a/pcbnew/tools/edit_tool.h b/pcbnew/tools/edit_tool.h index e7cb6cdd35..42f14b8771 100644 --- a/pcbnew/tools/edit_tool.h +++ b/pcbnew/tools/edit_tool.h @@ -128,6 +128,11 @@ public: */ int FilletTracks( const TOOL_EVENT& aEvent ); + /** + * Fillet (i.e. adds an arc tangent to) all selected straight lines by a user defined radius. + */ + int FilletLines( const TOOL_EVENT& aEvent ); + /** * Delete currently selected items. */ diff --git a/pcbnew/tools/pcb_actions.cpp b/pcbnew/tools/pcb_actions.cpp index 536017024d..a586f0aa3f 100644 --- a/pcbnew/tools/pcb_actions.cpp +++ b/pcbnew/tools/pcb_actions.cpp @@ -357,6 +357,10 @@ TOOL_ACTION PCB_ACTIONS::filletTracks( "pcbnew.InteractiveEdit.filletTracks", AS_GLOBAL, 0, "", _( "Fillet Tracks" ), _( "Adds arcs tangent to the selected straight track segments" ) ); +TOOL_ACTION PCB_ACTIONS::filletLines( "pcbnew.InteractiveEdit.filletLines", + AS_GLOBAL, 0, "", + _( "Fillet Lines" ), _( "Adds arcs tangent to the selected lines" ) ); + TOOL_ACTION PCB_ACTIONS::deleteFull( "pcbnew.InteractiveEdit.deleteFull", AS_GLOBAL, MD_SHIFT + WXK_DELETE, LEGACY_HK_NAME( "Delete Full Track" ), diff --git a/pcbnew/tools/pcb_actions.h b/pcbnew/tools/pcb_actions.h index 610fd29914..229598b46c 100644 --- a/pcbnew/tools/pcb_actions.h +++ b/pcbnew/tools/pcb_actions.h @@ -140,6 +140,7 @@ public: /// Fillet (i.e. adds an arc tangent to) all selected straight tracks by a user defined radius static TOOL_ACTION filletTracks; + static TOOL_ACTION filletLines; /// Activation of the edit tool static TOOL_ACTION properties;