diff --git a/libs/kimath/include/geometry/shape_poly_set.h b/libs/kimath/include/geometry/shape_poly_set.h index 24a018fa46..3d3de7229d 100644 --- a/libs/kimath/include/geometry/shape_poly_set.h +++ b/libs/kimath/include/geometry/shape_poly_set.h @@ -437,6 +437,13 @@ class SHAPE_POLY_SET : public SHAPE SHAPE_POLY_SET(); + /** + * Construct a SHAPE_POLY_SET with the first outline given by aOutline. + * + * @param aOutline is a closed outline + */ + SHAPE_POLY_SET( const SHAPE_LINE_CHAIN& aOutline ); + /** * Copy constructor SHAPE_POLY_SET * Performs a deep copy of \p aOther into \p this. diff --git a/libs/kimath/src/geometry/shape_poly_set.cpp b/libs/kimath/src/geometry/shape_poly_set.cpp index 84e6f1c97c..b1a87f4f73 100644 --- a/libs/kimath/src/geometry/shape_poly_set.cpp +++ b/libs/kimath/src/geometry/shape_poly_set.cpp @@ -60,6 +60,13 @@ SHAPE_POLY_SET::SHAPE_POLY_SET() : } +SHAPE_POLY_SET::SHAPE_POLY_SET( const SHAPE_LINE_CHAIN& aOutline ) : + SHAPE( SH_POLY_SET ) +{ + AddOutline( aOutline ); +} + + SHAPE_POLY_SET::SHAPE_POLY_SET( const SHAPE_POLY_SET& aOther, bool aDeepCopy ) : SHAPE( SH_POLY_SET ), m_polys( aOther.m_polys ) { diff --git a/pcbnew/class_zone.cpp b/pcbnew/class_zone.cpp index b5ef4fac06..73d14c7df1 100644 --- a/pcbnew/class_zone.cpp +++ b/pcbnew/class_zone.cpp @@ -689,6 +689,31 @@ bool ZONE_CONTAINER::HitTestFilledArea( const wxPoint& aRefPos ) const } +bool ZONE_CONTAINER::HitTestCutout( const VECTOR2I& aRefPos, int* aOutlineIdx, int* aHoleIdx ) const +{ + // Iterate over each outline polygon in the zone and then iterate over + // each hole it has to see if the point is in it. + for( int i = 0; i < m_Poly->OutlineCount(); i++ ) + { + for( int j = 0; j < m_Poly->HoleCount( i ); j++ ) + { + if( m_Poly->Hole( i, j ).PointInside( aRefPos ) ) + { + if( aOutlineIdx ) + *aOutlineIdx = i; + + if( aHoleIdx ) + *aHoleIdx = j; + + return true; + } + } + } + + return false; +} + + void ZONE_CONTAINER::GetMsgPanelInfo( EDA_UNITS aUnits, std::vector& aList ) { wxString msg; @@ -887,6 +912,21 @@ ZONE_CONNECTION ZONE_CONTAINER::GetPadConnection( D_PAD* aPad ) const } +void ZONE_CONTAINER::RemoveCutout( int aOutlineIdx, int aHoleIdx ) +{ + // Ensure the requested cutout is valid + if( m_Poly->OutlineCount() < aOutlineIdx || m_Poly->HoleCount( aOutlineIdx ) < aHoleIdx ) + return; + + SHAPE_POLY_SET cutPoly( m_Poly->Hole( aOutlineIdx, aHoleIdx ) ); + + // Add the cutout back to the zone + m_Poly->BooleanAdd( cutPoly, SHAPE_POLY_SET::PM_FAST ); + + SetNeedRefill( true ); +} + + void ZONE_CONTAINER::AddPolygon( const SHAPE_LINE_CHAIN& aPolygon ) { wxASSERT( aPolygon.IsClosed() ); diff --git a/pcbnew/class_zone.h b/pcbnew/class_zone.h index d1c3062e29..e4b6c76a18 100644 --- a/pcbnew/class_zone.h +++ b/pcbnew/class_zone.h @@ -283,6 +283,24 @@ public: */ bool HitTestFilledArea( const wxPoint& aRefPos ) const; + /** + * Tests if the given point is contained within a cutout of the zone. + * + * @param aRefPos is the point to test + * @param aOutlineIdx is the index of the outline containing the cutout + * @param aHoleIdx is the index of the hole + * @return true if aRefPos is inside a zone cutout + */ + bool HitTestCutout( const VECTOR2I& aRefPos, int* aOutlineIdx = nullptr, + int* aHoleIdx = nullptr ) const; + + bool HitTestCutout( const wxPoint& aRefPos, int* aOutlineIdx = nullptr, + int* aHoleIdx = nullptr ) const + { + return HitTestCutout( VECTOR2I( aRefPos.x, aRefPos.y ), aOutlineIdx, aHoleIdx ); + } + + /** * Some intersecting zones, despite being on the same layer with the same net, cannot be * merged due to other parameters such as fillet radius. The copper pour will end up @@ -618,6 +636,14 @@ public: void SetFilledPolysUseThickness( bool aOption ) { m_FilledPolysUseThickness = aOption; } + /** + * Remove a cutout from the zone. + * + * @param aOutlineIdx is the zone outline the hole belongs to + * @param aHoleIdx is the hole in the outline to remove + */ + void RemoveCutout( int aOutlineIdx, int aHoleIdx ); + /** * add a polygon to the zone outline * if the zone outline is empty, this is the main outline diff --git a/pcbnew/tools/edit_tool.cpp b/pcbnew/tools/edit_tool.cpp index 046c2a0ebb..5506ca0984 100644 --- a/pcbnew/tools/edit_tool.cpp +++ b/pcbnew/tools/edit_tool.cpp @@ -55,6 +55,7 @@ using namespace std::placeholders; #include #include #include +#include void EditToolSelectionFilter( GENERAL_COLLECTOR& aCollector, int aFlags ) @@ -925,6 +926,48 @@ int EDIT_TOOL::Remove( const TOOL_EVENT& aEvent ) } break; + case PCB_ZONE_AREA_T: + // We process the zones special so that cutouts can be deleted when the delete tool + // is called from inside a cutout when the zone is selected. + { + // Only interact with cutouts when deleting and a single item is selected + if( !isCut && selectionCopy.GetSize() == 1 ) + { + VECTOR2I curPos = getViewControls()->GetCursorPosition(); + auto zone = static_cast( item ); + + int outlineIdx, holeIdx; + + if( zone->HitTestCutout( curPos, &outlineIdx, &holeIdx ) ) + { + // Remove the cutout + m_commit->Modify( zone ); + zone->RemoveCutout( outlineIdx, holeIdx ); + + std::vector toFill; + toFill.emplace_back( zone ); + + // Fill the modified zone + ZONE_FILLER filler( board() ); + filler.InstallNewProgressReporter( frame(), _( "Fill Zone" ), 4 ); + filler.Fill( toFill ); + + // Update the display + zone->Hatch(); + canvas()->Refresh(); + + // Restore the selection on the original zone + m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, zone ); + + break; + } + } + + // Remove the entire zone otherwise + m_commit->Remove( item ); + } + break; + default: m_commit->Remove( item ); break;