diff --git a/include/tool/tool_menu.h b/include/tool/tool_menu.h index 373dec15c2..514c0103fb 100644 --- a/include/tool/tool_menu.h +++ b/include/tool/tool_menu.h @@ -89,6 +89,14 @@ public: */ void AddSubMenu( std::shared_ptr aSubMenu ); + /** + * @return the list of submenus from this menu + */ + std::vector >& GetSubMenus() + { + return m_subMenus; + } + /** * Function ShowContextMenu * diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index 09a66f769b..a04f721a0d 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -316,6 +316,7 @@ set( PCBNEW_CLASS_SRCS ratsnest/ratsnest.cpp + tools/convert_tool.cpp tools/drawing_tool.cpp tools/edit_tool.cpp tools/global_edit_tool.cpp diff --git a/pcbnew/class_track.h b/pcbnew/class_track.h index 142c4cb5e4..50f223ea8f 100644 --- a/pcbnew/class_track.h +++ b/pcbnew/class_track.h @@ -312,7 +312,6 @@ public: double GetAngle() const; double GetArcAngleStart() const; double GetArcAngleEnd() const; - virtual bool HitTest( const wxPoint& aPosition, int aAccuracy = 0 ) const override; virtual bool HitTest( const EDA_RECT& aRect, bool aContained = true, int aAccuracy = 0 ) const override; diff --git a/pcbnew/pcb_edit_frame.cpp b/pcbnew/pcb_edit_frame.cpp index fd9d279df0..0bad8e7c24 100644 --- a/pcbnew/pcb_edit_frame.cpp +++ b/pcbnew/pcb_edit_frame.cpp @@ -65,6 +65,7 @@ #include #include #include +#include #include #include #include @@ -503,6 +504,7 @@ void PCB_EDIT_FRAME::setupTools() m_toolManager->RegisterTool( new AUTOPLACE_TOOL ); m_toolManager->RegisterTool( new DRC ); m_toolManager->RegisterTool( new PCB_VIEWER_TOOLS ); + m_toolManager->RegisterTool( new CONVERT_TOOL ); m_toolManager->InitTools(); // Run the selection tool, it is supposed to be always active diff --git a/pcbnew/tools/convert_tool.cpp b/pcbnew/tools/convert_tool.cpp new file mode 100644 index 0000000000..b89aa055c1 --- /dev/null +++ b/pcbnew/tools/convert_tool.cpp @@ -0,0 +1,613 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 1992-2020 KiCad Developers, see AUTHORS.txt for contributors. + * @author Jon Evans + * + * 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 "convert_tool.h" + + +TOOL_ACTION PCB_ACTIONS::convertToPoly( "pcbnew.Convert.convertToPoly", + AS_GLOBAL, 0, "", _( "Convert to Polygon" ), + _( "Creates a graphic polygon from the selection" ), add_graphical_polygon_xpm ); + +TOOL_ACTION PCB_ACTIONS::convertToZone( "pcbnew.Convert.convertToZone", + AS_GLOBAL, 0, "", _( "Convert to Zone" ), _( "Creates a copper zone from the selection" ), + add_zone_xpm ); + +TOOL_ACTION PCB_ACTIONS::convertToKeepout( "pcbnew.Convert.convertToKeepout", + AS_GLOBAL, 0, "", _( "Convert to Keepout" ), + _( "Creates a keepout zone from the selection" ), add_keepout_area_xpm ); + +TOOL_ACTION PCB_ACTIONS::convertToLines( "pcbnew.Convert.convertToLines", + AS_GLOBAL, 0, "", _( "Convert to Lines" ), _( "Creates graphic lines from the selection" ), + add_line_xpm ); + +TOOL_ACTION PCB_ACTIONS::convertToArc( "pcbnew.Convert.convertToArc", + AS_GLOBAL, 0, "", _( "Convert to Arc" ), _( "Converts selected line segment to an arc" ), + add_arc_xpm ); + +TOOL_ACTION PCB_ACTIONS::convertToTracks( "pcbnew.Convert.convertToTracks", + AS_GLOBAL, 0, "", _( "Convert to Tracks" ), + _( "Converts selected graphic lines to tracks" ), add_tracks_xpm ); + + +CONVERT_TOOL::CONVERT_TOOL() : + TOOL_INTERACTIVE( "pcbnew.Convert" ), m_selectionTool( NULL ), + m_menu( NULL ), m_frame( NULL ) +{ +} + +CONVERT_TOOL::~CONVERT_TOOL() +{ + delete m_menu; +} + + +using S_C = SELECTION_CONDITIONS; +using P_S_C = PCB_SELECTION_CONDITIONS; + + +bool CONVERT_TOOL::Init() +{ + m_selectionTool = + static_cast( m_toolMgr->FindTool( "pcbnew.InteractiveSelection" ) ); + + if( !m_selectionTool ) + { + DisplayError( NULL, wxT( "pcbnew.InteractiveSelection tool is not available" ) ); + return false; + } + + m_frame = getEditFrame(); + + // Create a context menu and make it available through selection tool + m_menu = new CONDITIONAL_MENU( this ); + m_menu->SetIcon( refresh_xpm ); + m_menu->SetTitle( _( "Convert" ) ); + + static KICAD_T convertableTracks[] = { PCB_TRACE_T, PCB_ARC_T, EOT }; + + auto graphicLines = P_S_C::OnlyGraphicShapeTypes( { S_SEGMENT, S_RECT } ) && P_S_C::SameLayer(); + + auto trackLines = S_C::MoreThan( 1 ) && + S_C::OnlyTypes( convertableTracks ) && P_S_C::SameLayer(); + + auto anyLines = graphicLines || trackLines; + + auto anyPolys = ( S_C::OnlyType( PCB_ZONE_AREA_T ) || + P_S_C::OnlyGraphicShapeTypes( { S_POLYGON } ) ); + + auto showConvert = anyPolys || anyLines; + + m_menu->AddItem( PCB_ACTIONS::convertToPoly, anyLines ); + m_menu->AddItem( PCB_ACTIONS::convertToZone, anyLines ); + m_menu->AddItem( PCB_ACTIONS::convertToKeepout, anyLines ); + m_menu->AddItem( PCB_ACTIONS::convertToLines, anyPolys ); + m_menu->AddItem( PCB_ACTIONS::convertToTracks, anyPolys ); + // Not yet + // m_menu->AddItem( PCB_ACTIONS::convertToArc, lineToArc ); + + for( std::shared_ptr& subMenu : m_selectionTool->GetToolMenu().GetSubMenus() ) + { + if( dynamic_cast( subMenu.get() ) ) + static_cast( subMenu.get() )->AddMenu( m_menu, showConvert ); + } + + return true; +} + + +int CONVERT_TOOL::LinesToPoly( const TOOL_EVENT& aEvent ) +{ + auto& selection = m_selectionTool->RequestSelection( + []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool ) + { + EditToolSelectionFilter( aCollector, + EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS, sTool ); + + for( int i = aCollector.GetCount() - 1; i >= 0; --i ) + { + BOARD_ITEM* item = aCollector[i]; + + switch( item->Type() ) + { + case PCB_LINE_T: + switch( static_cast( item )->GetShape() ) + { + case S_SEGMENT: + case S_RECT: + //case S_ARC: // Not yet + break; + + default: + aCollector.Remove( item ); + } + + case PCB_TRACE_T: + // case PCB_ARC_T: // Not yet + break; + + default: + aCollector.Remove( item ); + } + } + } ); + + if( selection.Empty() ) + return 0; + + // TODO(JE) From a context menu, the selection condition enforces that the items are on + // a single layer. But, you can still trigger this with items on multiple layer selected. + // Technically we should make this work if each contiguous poly shares a layer + PCB_LAYER_ID destLayer = static_cast( selection.Front() )->GetLayer(); + + SHAPE_POLY_SET polySet = makePolysFromSegs( selection.GetItems() ); + + polySet.Append( makePolysFromRects( selection.GetItems() ) ); + + if( polySet.IsEmpty() ) + return 0; + + BOARD_COMMIT commit( m_frame ); + + // For now, we convert each outline in the returned shape to its own polygon + std::vector polys; + + for( int i = 0; i < polySet.OutlineCount(); i++ ) + polys.emplace_back( SHAPE_POLY_SET( polySet.COutline( i ) ) ); + + if( aEvent.IsAction( &PCB_ACTIONS::convertToPoly ) ) + { + for( const SHAPE_POLY_SET& poly : polys ) + { + DRAWSEGMENT* graphic = new DRAWSEGMENT; + + graphic->SetShape( S_POLYGON ); + graphic->SetLayer( destLayer ); + graphic->SetPolyShape( poly ); + + commit.Add( graphic ); + } + + commit.Push( _( "Convert shapes to polygon" ) ); + } + else + { + // Creating zone or keepout + PCB_BASE_EDIT_FRAME* frame = getEditFrame(); + BOARD_ITEM_CONTAINER* parent = frame->GetModel(); + ZONE_SETTINGS zoneInfo = frame->GetZoneSettings(); + + int ret; + + if( aEvent.IsAction( &PCB_ACTIONS::convertToKeepout ) ) + ret = InvokeKeepoutAreaEditor( frame, &zoneInfo ); + else + ret = InvokeCopperZonesEditor( frame, &zoneInfo ); + + if( ret == wxID_CANCEL ) + return 0; + + for( const SHAPE_POLY_SET& poly : polys ) + { + ZONE_CONTAINER* zone = new ZONE_CONTAINER( parent ); + + *zone->Outline() = poly; + zone->HatchBorder(); + + zoneInfo.ExportSetting( *zone ); + + commit.Add( zone ); + } + + commit.Push( _( "Convert shapes to zone" ) ); + } + + return 0; +} + + +SHAPE_POLY_SET CONVERT_TOOL::makePolysFromSegs( const std::deque& aItems ) +{ + SHAPE_POLY_SET poly; + + std::map> connections; + std::set used; + std::deque toCheck; + + for( EDA_ITEM* item : aItems ) + { + if( OPT seg = getStartEndPoints( item ) ) + { + toCheck.push_back( item ); + connections[seg->A].emplace_back( item ); + connections[seg->B].emplace_back( item ); + } + } + + while( !toCheck.empty() ) + { + EDA_ITEM* candidate = toCheck.front(); + toCheck.pop_front(); + + if( used.count( candidate ) ) + continue; + + OPT seg = getStartEndPoints( candidate ); + wxASSERT( seg ); + + SHAPE_LINE_CHAIN outline; + std::deque points; + + // aDirection == true for walking "right" and appending to the end of points + // false for walking "left" and prepending to the beginning + std::function process = + [&]( EDA_ITEM* aItem, bool aDirection ) + { + if( used.count( aItem ) ) + return; + + used.insert( aItem ); + + OPT nextSeg = getStartEndPoints( aItem ); + wxASSERT( nextSeg ); + + // The reference point, i.e. last added point in the direction we're headed + VECTOR2I& ref = aDirection ? points.back() : points.front(); + + // The next point, i.e. the other point on this segment + VECTOR2I& next = ( ref == nextSeg->A ) ? nextSeg->B : nextSeg->A; + + if( aDirection ) + points.push_back( next ); + else + points.push_front( next ); + + for( EDA_ITEM* neighbor : connections[next] ) + process( neighbor, aDirection ); + }; + + // Start with just one point and walk one direction + points.push_back( seg->A ); + process( candidate, true ); + + // check for any candidates on the "left" + EDA_ITEM* left = nullptr; + + for( EDA_ITEM* possibleLeft : connections[seg->A] ) + { + if( possibleLeft != candidate ) + { + left = possibleLeft; + break; + } + } + + if( left ) + process( left, false ); + + if( points.size() < 3 ) + continue; + + for( const VECTOR2I& point : points ) + outline.Append( point ); + + outline.SetClosed( true ); + + poly.AddOutline( outline ); + } + + return poly; +} + + +SHAPE_POLY_SET CONVERT_TOOL::makePolysFromRects( const std::deque& aItems ) +{ + SHAPE_POLY_SET poly; + + for( EDA_ITEM* item : aItems ) + { + if( item->Type() != PCB_LINE_T ) + continue; + + DRAWSEGMENT* graphic = static_cast( item ); + + if( graphic->GetShape() != S_RECT ) + continue; + + SHAPE_LINE_CHAIN outline; + VECTOR2I start( graphic->GetStart() ); + VECTOR2I end( graphic->GetEnd() ); + + outline.Append( start ); + outline.Append( VECTOR2I( end.x, start.y ) ); + outline.Append( end ); + outline.Append( VECTOR2I( start.x, end.y ) ); + outline.SetClosed( true ); + + poly.AddOutline( outline ); + } + + return poly; +} + + +int CONVERT_TOOL::PolyToLines( const TOOL_EVENT& aEvent ) +{ + auto& selection = m_selectionTool->RequestSelection( + []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool ) + { + EditToolSelectionFilter( aCollector, + EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS, sTool ); + + for( int i = aCollector.GetCount() - 1; i >= 0; --i ) + { + BOARD_ITEM* item = aCollector[i]; + + switch( item->Type() ) + { + case PCB_LINE_T: + switch( static_cast( item )->GetShape() ) + { + case S_POLYGON: + break; + + default: + aCollector.Remove( item ); + } + + case PCB_ZONE_AREA_T: + break; + + default: + aCollector.Remove( item ); + } + } + } ); + + if( selection.Empty() ) + return 0; + + auto getPolySet = + []( EDA_ITEM* aItem ) + { + SHAPE_POLY_SET set; + + switch( aItem->Type() ) + { + case PCB_ZONE_AREA_T: + set = *static_cast( aItem )->Outline(); + break; + + case PCB_LINE_T: + wxASSERT( static_cast( aItem )->GetShape() == S_POLYGON ); + set = static_cast( aItem )->GetPolyShape(); + break; + + default: + wxFAIL_MSG( "Unhandled type in PolyToLines - getPolySet" ); + break; + } + + return set; + }; + + auto getSegList = + []( SHAPE_POLY_SET& aPoly ) + { + std::vector segs; + + // Our input should be valid polys, so OK to assert here + wxASSERT( aPoly.VertexCount() >= 2 ); + + for( int i = 1; i < aPoly.VertexCount(); i++ ) + segs.emplace_back( SEG( aPoly.CVertex( i - 1 ), aPoly.CVertex( i ) ) ); + + segs.emplace_back( SEG( aPoly.CVertex( aPoly.VertexCount() - 1 ), + aPoly.CVertex( 0 ) ) ); + + return segs; + }; + + BOARD_COMMIT commit( m_frame ); + + for( EDA_ITEM* item : selection ) + { + PCB_LAYER_ID layer = static_cast( item )->GetLayer(); + SHAPE_POLY_SET polySet = getPolySet( item ); + std::vector segs = getSegList( polySet ); + + if( aEvent.IsAction( &PCB_ACTIONS::convertToLines ) ) + { + for( SEG& seg : segs ) + { + DRAWSEGMENT* graphic = new DRAWSEGMENT; + + graphic->SetShape( S_SEGMENT ); + graphic->SetLayer( layer ); + graphic->SetStart( wxPoint( seg.A ) ); + graphic->SetEnd( wxPoint( seg.B ) ); + + commit.Add( graphic ); + } + } + else + { + PCB_BASE_EDIT_FRAME* frame = getEditFrame(); + BOARD_ITEM_CONTAINER* parent = frame->GetModel(); + + if( !IsCopperLayer( layer ) ) + layer = frame->SelectLayer( F_Cu, LSET::AllNonCuMask() ); + + // Creating tracks + for( SEG& seg : segs ) + { + TRACK* track = new TRACK( parent ); + + track->SetLayer( layer ); + track->SetStart( wxPoint( seg.A ) ); + track->SetEnd( wxPoint( seg.B ) ); + + commit.Add( track ); + } + } + } + + commit.Push( _( "Convert polygons to lines" ) ); + + return 0; +} + + +int CONVERT_TOOL::SegmentToArc( const TOOL_EVENT& aEvent ) +{ + auto& selection = m_selectionTool->RequestSelection( + []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool ) + { + EditToolSelectionFilter( aCollector, + EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS, sTool ); + + for( int i = aCollector.GetCount() - 1; i >= 0; --i ) + { + BOARD_ITEM* item = aCollector[i]; + + if( !( item->Type() == PCB_LINE_T || item->Type() == PCB_TRACE_T ) ) + aCollector.Remove( item ); + } + } ); + + EDA_ITEM* source = selection.Front(); + wxPoint start, end, mid; + + if( OPT optSeg = getStartEndPoints( source ) ) + { + start = wxPoint( optSeg->A ); + end = wxPoint( optSeg->B ); + mid = wxPoint( optSeg->Center() ); + } + else + return -1; + + PCB_BASE_EDIT_FRAME* frame = getEditFrame(); + BOARD_ITEM_CONTAINER* parent = frame->GetModel(); + + BOARD_ITEM* boardItem = dynamic_cast( source ); + wxASSERT( boardItem ); + + PCB_LAYER_ID layer = boardItem->GetLayer(); + + BOARD_COMMIT commit( m_frame ); + + if( source->Type() == PCB_LINE_T ) + { + DRAWSEGMENT* line = static_cast( source ); + DRAWSEGMENT* arc = new DRAWSEGMENT( parent ); + + wxPoint center = GetArcCenter( start, mid, end ); + + arc->SetShape( S_ARC ); + arc->SetLayer( layer ); + arc->SetWidth( line->GetWidth() ); + arc->SetArcStart( start ); + arc->SetCenter( center ); + // TODO(JE): once !325 is merged + //arc->SetArcEnd( end ); + + commit.Add( arc ); + } + else + { + wxASSERT( source->Type() == PCB_TRACE_T ); + TRACK* line = static_cast( source ); + ARC* arc = new ARC( parent ); + + arc->SetLayer( layer ); + arc->SetWidth( line->GetWidth() ); + arc->SetStart( start ); + arc->SetMid( mid ); + arc->SetEnd( end ); + + commit.Add( arc ); + } + + commit.Push( _( "Create arc from line segment" ) ); + + return 0; +} + + +OPT CONVERT_TOOL::getStartEndPoints( EDA_ITEM* aItem ) +{ + switch( aItem->Type() ) + { + case PCB_LINE_T: + { + DRAWSEGMENT* line = static_cast( aItem ); + return boost::make_optional( { VECTOR2I( line->GetStart() ), + VECTOR2I( line->GetEnd() ) } ); + } + + case PCB_TRACE_T: + { + TRACK* line = static_cast( aItem ); + return boost::make_optional( { VECTOR2I( line->GetStart() ), + VECTOR2I( line->GetEnd() ) } ); + } + + case PCB_ARC_T: + { + ARC* arc = static_cast( aItem ); + return boost::make_optional( { VECTOR2I( arc->GetStart() ), + VECTOR2I( arc->GetEnd() ) } ); + } + + default: + return NULLOPT; + } +} + + +void CONVERT_TOOL::setTransitions() +{ + Go( &CONVERT_TOOL::LinesToPoly, PCB_ACTIONS::convertToPoly.MakeEvent() ); + Go( &CONVERT_TOOL::LinesToPoly, PCB_ACTIONS::convertToZone.MakeEvent() ); + Go( &CONVERT_TOOL::LinesToPoly, PCB_ACTIONS::convertToKeepout.MakeEvent() ); + Go( &CONVERT_TOOL::PolyToLines, PCB_ACTIONS::convertToLines.MakeEvent() ); + Go( &CONVERT_TOOL::PolyToLines, PCB_ACTIONS::convertToTracks.MakeEvent() ); + Go( &CONVERT_TOOL::SegmentToArc, PCB_ACTIONS::convertToArc.MakeEvent() ); +} diff --git a/pcbnew/tools/convert_tool.h b/pcbnew/tools/convert_tool.h new file mode 100644 index 0000000000..5f70642cde --- /dev/null +++ b/pcbnew/tools/convert_tool.h @@ -0,0 +1,98 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 1992-2020 KiCad Developers, see AUTHORS.txt for contributors. + * @author Jon Evans + * + * 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 + */ + +#ifndef CONVERT_TOOL_H_ +#define CONVERT_TOOL_H_ + +#include + +class CONDITIONAL_MENU; +class SELECTION_TOOL; +class PCB_BASE_FRAME; + + +class CONVERT_TOOL : public TOOL_INTERACTIVE +{ +public: + CONVERT_TOOL(); + virtual ~CONVERT_TOOL(); + + /// @copydoc TOOL_INTERACTIVE::Reset() + void Reset( RESET_REASON aReason ) override {} + + /// @copydoc TOOL_INTERACTIVE::Init() + bool Init() override; + + /** + * Converts selected lines to a polygon, if possible + */ + int LinesToPoly( const TOOL_EVENT& aEvent ); + + /** + * Converts selected polygon-like object to graphic lines, if possible + */ + int PolyToLines( const TOOL_EVENT& aEvent ); + + /** + * Converts selected segment (graphic or track) to an arc of the same type + */ + int SegmentToArc( const TOOL_EVENT& aEvent ); + + ///> @copydoc TOOL_INTERACTIVE::setTransitions() + void setTransitions() override; + +private: + + /** + * Retrieves the start and end points for a generic item + * @param aItem is an item that has a start and end point + * @return a segment from start to end, or NULLOPT if invalid + */ + static OPT getStartEndPoints( EDA_ITEM* aItem ); + + /** + * Tries to make polygons from segments in the selected items. + * Polygons are formed from chains of lines/arcs. Each set containing two or more lines/arcs + * that are connected will be added to the return SHAPE_POLY_SET as an outline. + * No attempt is made to guess at holes. + * @param aItems is a list of items to process + * @return a SHAPE_POLY_SET containing any polygons that were created + */ + static SHAPE_POLY_SET makePolysFromSegs( const std::deque& aItems ); + + /** + * Tries to make polygons from rects + * @param aItems is a list of rect shapes to process + * @return a SHAPE_POLY_SET containing any polygons that were created + */ + static SHAPE_POLY_SET makePolysFromRects( const std::deque& aItems ); + + SELECTION_TOOL* m_selectionTool; + + CONDITIONAL_MENU* m_menu; + + PCB_BASE_FRAME* m_frame; +}; + +#endif diff --git a/pcbnew/tools/edit_tool.cpp b/pcbnew/tools/edit_tool.cpp index 58f35cef64..2f2f56e943 100644 --- a/pcbnew/tools/edit_tool.cpp +++ b/pcbnew/tools/edit_tool.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -133,26 +134,17 @@ void EDIT_TOOL::Reset( RESET_REASON aReason ) } -class SPECIAL_TOOLS_CONTEXT_MENU : public ACTION_MENU +SPECIAL_TOOLS_CONTEXT_MENU::SPECIAL_TOOLS_CONTEXT_MENU( TOOL_INTERACTIVE* aTool ) : + CONDITIONAL_MENU( aTool ) { -public: - SPECIAL_TOOLS_CONTEXT_MENU() : - ACTION_MENU( true ) - { - SetIcon( options_board_xpm ); - SetTitle( _( "Special Tools..." ) ); + SetIcon( options_board_xpm ); + SetTitle( _( "Special Tools..." ) ); - Add( PCB_ACTIONS::moveExact ); - Add( PCB_ACTIONS::moveWithReference ); - Add( PCB_ACTIONS::positionRelative ); - Add( PCB_ACTIONS::createArray ); - } - - ACTION_MENU* create() const override - { - return new SPECIAL_TOOLS_CONTEXT_MENU(); - } -}; + AddItem( PCB_ACTIONS::moveExact, SELECTION_CONDITIONS::ShowAlways ); + AddItem( PCB_ACTIONS::moveWithReference, SELECTION_CONDITIONS::ShowAlways ); + AddItem( PCB_ACTIONS::positionRelative, SELECTION_CONDITIONS::ShowAlways ); + AddItem( PCB_ACTIONS::createArray, SELECTION_CONDITIONS::ShowAlways ); +} bool EDIT_TOOL::Init() @@ -209,8 +201,7 @@ bool EDIT_TOOL::Init() menu.AddItem( ACTIONS::duplicate, SELECTION_CONDITIONS::NotEmpty ); // Add the submenu for create array and special move - auto specialToolsSubMenu = std::make_shared(); - specialToolsSubMenu->SetTool( this ); + auto specialToolsSubMenu = std::make_shared( this ); menu.AddSeparator(); m_selectionTool->GetToolMenu().AddSubMenu( specialToolsSubMenu ); menu.AddMenu( specialToolsSubMenu.get(), SELECTION_CONDITIONS::NotEmpty ); diff --git a/pcbnew/tools/edit_tool.h b/pcbnew/tools/edit_tool.h index 840d1ed334..a8b7c5831d 100644 --- a/pcbnew/tools/edit_tool.h +++ b/pcbnew/tools/edit_tool.h @@ -57,7 +57,14 @@ namespace KIGFX { void EditToolSelectionFilter( GENERAL_COLLECTOR& aCollector, int aFlags, SELECTION_TOOL* sTool ); -/** + +class SPECIAL_TOOLS_CONTEXT_MENU : public CONDITIONAL_MENU +{ +public: + SPECIAL_TOOLS_CONTEXT_MENU( TOOL_INTERACTIVE* aTool ); +}; + + /** * EDIT_TOOL * * The interactive edit tool. Allows one to move, rotate, flip and change properties of items selected diff --git a/pcbnew/tools/pcb_actions.h b/pcbnew/tools/pcb_actions.h index 8351eea8d1..685b8b656a 100644 --- a/pcbnew/tools/pcb_actions.h +++ b/pcbnew/tools/pcb_actions.h @@ -441,6 +441,14 @@ public: static TOOL_ACTION autoplaceOffboardComponents; static TOOL_ACTION autoplaceSelectedComponents; + // convert tool + static TOOL_ACTION convertToPoly; + static TOOL_ACTION convertToZone; + static TOOL_ACTION convertToKeepout; + static TOOL_ACTION convertToLines; + static TOOL_ACTION convertToArc; + static TOOL_ACTION convertToTracks; + ///> @copydoc COMMON_ACTIONS::TranslateLegacyId() virtual OPT TranslateLegacyId( int aId ) override; }; diff --git a/pcbnew/tools/pcb_selection_conditions.cpp b/pcbnew/tools/pcb_selection_conditions.cpp index d239f46320..99c202bd0c 100644 --- a/pcbnew/tools/pcb_selection_conditions.cpp +++ b/pcbnew/tools/pcb_selection_conditions.cpp @@ -25,10 +25,13 @@ #include "pcb_selection_conditions.h" #include "selection_tool.h" #include +#include #include using namespace std::placeholders; +using S_C = SELECTION_CONDITION; + bool PCB_SELECTION_CONDITIONS::OnlyConnectedItems( const SELECTION& aSelection ) { @@ -47,18 +50,24 @@ bool PCB_SELECTION_CONDITIONS::OnlyConnectedItems( const SELECTION& aSelection ) } -SELECTION_CONDITION PCB_SELECTION_CONDITIONS::SameNet( bool aAllowUnconnected ) +S_C PCB_SELECTION_CONDITIONS::SameNet( bool aAllowUnconnected ) { return std::bind( &PCB_SELECTION_CONDITIONS::sameNetFunc, _1, aAllowUnconnected ); } -SELECTION_CONDITION PCB_SELECTION_CONDITIONS::SameLayer() +S_C PCB_SELECTION_CONDITIONS::SameLayer() { return std::bind( &PCB_SELECTION_CONDITIONS::sameLayerFunc, _1 ); } +S_C PCB_SELECTION_CONDITIONS::OnlyGraphicShapeTypes( const std::set aTypes ) +{ + return std::bind( &PCB_SELECTION_CONDITIONS::onlyGraphicShapeTypesFunc, _1, aTypes ); +} + + bool PCB_SELECTION_CONDITIONS::sameNetFunc( const SELECTION& aSelection, bool aAllowUnconnected ) { @@ -125,3 +134,24 @@ bool PCB_SELECTION_CONDITIONS::sameLayerFunc( const SELECTION& aSelection ) return true; } + + +bool PCB_SELECTION_CONDITIONS::onlyGraphicShapeTypesFunc( const SELECTION& aSelection, + const std::set aTypes ) +{ + if( aSelection.Empty() ) + return false; + + for( const EDA_ITEM* item : aSelection ) + { + if( item->Type() != PCB_LINE_T ) + return false; + + STROKE_T shape = static_cast( item )->GetShape(); + + if( !aTypes.count( shape ) ) + return false; + } + + return true; +} diff --git a/pcbnew/tools/pcb_selection_conditions.h b/pcbnew/tools/pcb_selection_conditions.h index fc3c227819..db102ba42f 100644 --- a/pcbnew/tools/pcb_selection_conditions.h +++ b/pcbnew/tools/pcb_selection_conditions.h @@ -60,6 +60,14 @@ public: */ static SELECTION_CONDITION SameLayer(); + /** + * Creates a functor that tests if the selection contains DRAWSEGMENT* items of certain shapes + * This implicitly includes an OnlyType( PCB_LINE_T ) as part of the test + * @param aTypes is a list of allowed DRAWSEGMENT shapes (@see STROKE_T) + * @return functor testing if selected items match the given list of allowed shapes + */ + static SELECTION_CONDITION OnlyGraphicShapeTypes( const std::set aTypes ); + private: ///> Helper function used by SameNet() @@ -67,6 +75,10 @@ private: ///> Helper function used by SameLayer() static bool sameLayerFunc( const SELECTION& aSelection ); + + ///> Helper function used by OnlyGraphicShapeTypes() + static bool onlyGraphicShapeTypesFunc( const SELECTION& aSelection, + const std::set aTypes ); }; #endif /* PCB_SELECTION_CONDITIONS_H_ */