diff --git a/pcbnew/tools/common_actions.cpp b/pcbnew/tools/common_actions.cpp index f913534208..05bf2cb97d 100644 --- a/pcbnew/tools/common_actions.cpp +++ b/pcbnew/tools/common_actions.cpp @@ -189,6 +189,11 @@ TOOL_ACTION COMMON_ACTIONS::drawKeepout( "pcbnew.InteractiveDrawing.keepout", AS_GLOBAL, 0, _( "Add Keepout Area" ), _( "Add a keepout area" ), NULL, AF_ACTIVATE ); +TOOL_ACTION COMMON_ACTIONS::drawZoneCutout( "pcbnew.InteractiveDrawing.zoneCutout", + AS_GLOBAL, 0, + _( "Add a Zone Cutout" ), _( "Add a cutout area of an existing zone" ), + add_zone_cutout_xpm, AF_ACTIVATE ); + TOOL_ACTION COMMON_ACTIONS::placeDXF( "pcbnew.InteractiveDrawing.placeDXF", AS_GLOBAL, 0, "Place DXF", "", NULL, AF_ACTIVATE ); diff --git a/pcbnew/tools/common_actions.h b/pcbnew/tools/common_actions.h index 7f69c1be9d..d4eaedbb03 100644 --- a/pcbnew/tools/common_actions.h +++ b/pcbnew/tools/common_actions.h @@ -126,6 +126,9 @@ public: /// Activation of the drawing tool (drawing a keepout area) static TOOL_ACTION drawKeepout; + /// Activation of the drawing tool (drawing a ZONE cutout) + static TOOL_ACTION drawZoneCutout; + /// Activation of the drawing tool (placing a TARGET) static TOOL_ACTION placeTarget; diff --git a/pcbnew/tools/drawing_tool.cpp b/pcbnew/tools/drawing_tool.cpp index b228df17b9..5a31d3d085 100644 --- a/pcbnew/tools/drawing_tool.cpp +++ b/pcbnew/tools/drawing_tool.cpp @@ -545,7 +545,7 @@ int DRAWING_TOOL::DrawZone( const TOOL_EVENT& aEvent ) SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::ZONE ); m_frame->SetToolID( ID_PCB_ZONES_BUTT, wxCURSOR_PENCIL, _( "Add zones" ) ); - return drawZone( false ); + return drawZone( false, ZONE_MODE::ADD ); } @@ -554,7 +554,16 @@ int DRAWING_TOOL::DrawKeepout( const TOOL_EVENT& aEvent ) SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::KEEPOUT ); m_frame->SetToolID( ID_PCB_KEEPOUT_AREA_BUTT, wxCURSOR_PENCIL, _( "Add keepout" ) ); - return drawZone( true ); + return drawZone( true, ZONE_MODE::ADD ); +} + + +int DRAWING_TOOL::DrawZoneCutout( const TOOL_EVENT& aEvent ) +{ + SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::ZONE ); + m_frame->SetToolID( ID_PCB_KEEPOUT_AREA_BUTT, wxCURSOR_PENCIL, _( "Add zone cutout" ) ); + + return drawZone( false, ZONE_MODE::CUTOUT ); } @@ -1186,12 +1195,89 @@ std::unique_ptr DRAWING_TOOL::createNewZone( } -int DRAWING_TOOL::drawZone( bool aKeepout ) +std::unique_ptr DRAWING_TOOL::createZoneFromExisting( + const ZONE_CONTAINER& aSrcZone ) +{ + auto newZone = std::make_unique( m_board ); + + ZONE_SETTINGS zoneSettings; + zoneSettings << aSrcZone; + + zoneSettings.ExportSetting( *newZone ); + + return newZone; +} + + +bool DRAWING_TOOL::getSourceZoneForAction( ZONE_MODE aMode, + ZONE_CONTAINER*& aZone ) +{ + aZone = nullptr; + + // not an action that needs a source zone + if( aMode == ZONE_MODE::ADD ) + return true; + + SELECTION_TOOL* selTool = m_toolMgr->GetTool(); + const SELECTION& selection = selTool->GetSelection(); + + if( selection.Empty() ) + m_toolMgr->RunAction( COMMON_ACTIONS::selectionCursor, true ); + + // we want a single zone + if( selection.Size() != 1 ) + return false; + + aZone = dyn_cast( selection[0] ); + + // expected a zone, but didn't get one + if( !aZone ) + return false; + + return true; +} + + +void DRAWING_TOOL::performZoneCutout( ZONE_CONTAINER& aExistingZone, + ZONE_CONTAINER& cutout ) +{ + // Copy cutout corners into existing zone + for( int ii = 0; ii < cutout.GetNumCorners(); ii++ ) + { + aExistingZone.AppendCorner( cutout.GetCornerPosition( ii ) ); + } + + // Close the current corner list + aExistingZone.Outline()->CloseLastContour(); + + m_board->OnAreaPolygonModified( nullptr, &aExistingZone ); + + // Re-fill if needed + if( aExistingZone.IsFilled() ) + { + SELECTION_TOOL* selTool = m_toolMgr->GetTool(); + + auto& selection = selTool->GetSelection(); + + selection.Clear(); + selection.Add( &aExistingZone ); + + m_toolMgr->RunAction( COMMON_ACTIONS::zoneFill, true ); + } +} + + +int DRAWING_TOOL::drawZone( bool aKeepout, ZONE_MODE aMode ) { std::unique_ptr zone; DRAWSEGMENT line45; DRAWSEGMENT* helperLine = NULL; // we will need more than one helper line BOARD_COMMIT commit( m_frame ); + ZONE_CONTAINER* sourceZone = nullptr; + + // get a source zone, if we need one + if( !getSourceZoneForAction( aMode, sourceZone ) ) + return 0; // Add a VIEW_GROUP that serves as a preview for the new item SELECTION preview; @@ -1279,8 +1365,21 @@ int DRAWING_TOOL::drawZone( bool aKeepout ) if( !aKeepout ) static_cast( m_frame )->Fill_Zone( zone.get() ); - commit.Add( zone.release() ); - commit.Push( _( "Draw a zone" ) ); + if ( aMode == ZONE_MODE::CUTOUT ) + { + // For cutouts, subtract from the source + commit.Modify( sourceZone ); + + performZoneCutout( *sourceZone, *zone ); + + commit.Push( _( "Add a zone cutout" ) ); + } + else + { + // Add the zone as a new board item + commit.Add( zone.release() ); + commit.Push( _( "Draw a zone" ) ); + } } // if kept, this was released. if still not null, @@ -1304,7 +1403,14 @@ int DRAWING_TOOL::drawZone( bool aKeepout ) { if( numPoints == 0 ) // it's the first click { - zone = createNewZone( aKeepout ); + if( sourceZone ) + { + zone = createZoneFromExisting( *sourceZone ); + } + else + { + zone = createNewZone( aKeepout ); + } if( !zone ) { @@ -1397,6 +1503,7 @@ void DRAWING_TOOL::SetTransitions() Go( &DRAWING_TOOL::DrawDimension, COMMON_ACTIONS::drawDimension.MakeEvent() ); Go( &DRAWING_TOOL::DrawZone, COMMON_ACTIONS::drawZone.MakeEvent() ); Go( &DRAWING_TOOL::DrawKeepout, COMMON_ACTIONS::drawKeepout.MakeEvent() ); + Go( &DRAWING_TOOL::DrawZoneCutout, COMMON_ACTIONS::drawZoneCutout.MakeEvent() ); Go( &DRAWING_TOOL::PlaceText, COMMON_ACTIONS::placeText.MakeEvent() ); Go( &DRAWING_TOOL::PlaceDXF, COMMON_ACTIONS::placeDXF.MakeEvent() ); Go( &DRAWING_TOOL::SetAnchor, COMMON_ACTIONS::setAnchor.MakeEvent() ); diff --git a/pcbnew/tools/drawing_tool.h b/pcbnew/tools/drawing_tool.h index 3bc396c214..287acae9d0 100644 --- a/pcbnew/tools/drawing_tool.h +++ b/pcbnew/tools/drawing_tool.h @@ -143,6 +143,15 @@ public: */ int DrawKeepout( const TOOL_EVENT& aEvent ); + /** + * Function DrawZoneCutout() + * Starts interactively drawing a zone cutout area of an existing zone. + * The normal zone interactive tool is used, but the zone settings + * dialog is not shown (since the cutout affects only shape of an + * existing zone). + */ + int DrawZoneCutout( const TOOL_EVENT& aEvent ); + /** * Function PlaceDXF() * Places a drawing imported from a DXF file in module editor. @@ -159,6 +168,13 @@ public: void SetTransitions() override; private: + + enum class ZONE_MODE + { + ADD, ///< Add a new zone/keepout with fresh settings + CUTOUT, ///< Make a cutout to an existing zone + }; + ///> Shows the context menu for the drawing tool ///> This menu consists of normal UI functions (zoom, grid, etc) ///> And any suitable global functions for the active drawing type. @@ -180,9 +196,14 @@ private: ///> the same point. bool drawArc( DRAWSEGMENT*& aGraphic ); - ///> Draws a polygon, that is added as a zone or a keepout area. - ///> @param aKeepout decides if the drawn polygon is a zone or a keepout area. - int drawZone( bool aKeepout ); + /** + * Draws a polygon, that is added as a zone or a keepout area. + * + * @param aKeepout dictates if the drawn polygon is a zone or a + * keepout area. + * @param aMode dictates the mode of the zone tool + */ + int drawZone( bool aKeepout, ZONE_MODE aMode ); /** * Function createNewZone() @@ -195,6 +216,44 @@ private: */ std::unique_ptr createNewZone( bool aKeepout ); + /** + * Function createZoneFromExisting + * + * Create a new zone with the settings from an existing zone + * + * @param aSrcZone the zone to copy settings from + * @return the new zone + */ + std::unique_ptr createZoneFromExisting( + const ZONE_CONTAINER& aSrcZone ); + + /** + * Function getSourceZoneForAction() + * + * Gets a source zone item for an action that takes an existing zone + * into account (for example a cutout of an existing zone). The source + * zone is taken from the current selection + * + * @param aMode mode of the zone tool + * @param aZone updated pointer to a suitable source zone, + * or nullptr if none found, or the action doesn't need a source + * @return true if a suitable zone was found, or the action doesn't + * need a zone. False if the action needs a zone but none was found. + */ + bool getSourceZoneForAction( ZONE_MODE aMode, ZONE_CONTAINER*& aZone ); + + /** + * Function performZoneCutout() + * + * Cut one zone out of another one (i.e. subtraction) and + * update the zone. + * + * @param aExistingZone the zone to removed area from + * @param aCutout the area to remove + */ + void performZoneCutout( ZONE_CONTAINER& aExistingZone, + ZONE_CONTAINER& cutout ); + /** * Function make45DegLine() * Forces a DRAWSEGMENT to be drawn at multiple of 45 degrees. The origin stays the same, diff --git a/pcbnew/tools/pcb_editor_control.cpp b/pcbnew/tools/pcb_editor_control.cpp index 46e82cafa4..5591de1c27 100644 --- a/pcbnew/tools/pcb_editor_control.cpp +++ b/pcbnew/tools/pcb_editor_control.cpp @@ -74,6 +74,7 @@ public: Add( COMMON_ACTIONS::zoneMerge ); Add( COMMON_ACTIONS::zoneDuplicate ); + Add( COMMON_ACTIONS::drawZoneCutout ); } protected: @@ -93,6 +94,7 @@ private: )( selTool->GetSelection() ); Enable( getMenuId( COMMON_ACTIONS::zoneDuplicate), singleZoneActionsEnabled ); + Enable( getMenuId( COMMON_ACTIONS::drawZoneCutout), singleZoneActionsEnabled ); // enable zone actions that ably to a specific set of zones (as opposed to all of them) bool nonGlobalActionsEnabled = ( SELECTION_CONDITIONS::MoreThan( 0 ) )( selTool->GetSelection() );