From ac095b67246d13d6d2eb89d92d6388c78d3ea8e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20W=C5=82ostowski?= Date: Thu, 19 Oct 2017 23:16:06 +0200 Subject: [PATCH] pcbnew: tools for converting between custom-shaped pads and graphical shapes --- pcbnew/class_drawsegment.cpp | 69 +++++- pcbnew/class_drawsegment.h | 16 +- pcbnew/class_edge_mod.cpp | 24 +- pcbnew/class_pad.h | 8 +- pcbnew/class_pad_custom_shape_functions.cpp | 193 +++++++++++++--- .../dialog_graphic_item_properties.cpp | 3 + pcbnew/dialogs/dialog_pad_properties.cpp | 8 +- pcbnew/librairi.cpp | 9 +- pcbnew/microwave.cpp | 3 +- pcbnew/tools/edit_tool.cpp | 5 +- pcbnew/tools/module_editor_tools.cpp | 207 ++++++++++++++++++ pcbnew/tools/module_editor_tools.h | 15 ++ pcbnew/tools/pad_tool.cpp | 1 + 13 files changed, 490 insertions(+), 71 deletions(-) diff --git a/pcbnew/class_drawsegment.cpp b/pcbnew/class_drawsegment.cpp index 38bef2b070..8c6a6951ba 100644 --- a/pcbnew/class_drawsegment.cpp +++ b/pcbnew/class_drawsegment.cpp @@ -79,9 +79,9 @@ void DRAWSEGMENT::Rotate( const wxPoint& aRotCentre, double aAngle ) break; case S_POLYGON: - for( unsigned ii = 0; ii < m_PolyPoints.size(); ii++ ) + for( auto iter = m_Poly.Iterate(); iter; iter++ ) { - RotatePoint( &m_PolyPoints[ii], aRotCentre, aAngle); + RotatePoint( *iter, VECTOR2I(aRotCentre), aAngle); } break; @@ -411,10 +411,12 @@ const EDA_RECT DRAWSEGMENT::GetBoundingBox() const { wxPoint p_end; MODULE* module = GetParentModule(); + bool first = true; - for( unsigned ii = 0; ii < m_PolyPoints.size(); ii++ ) + for( auto iter = m_Poly.CIterate(); iter; iter++ ) { - wxPoint pt = m_PolyPoints[ii]; + wxPoint pt ( iter->x, iter->y ); + if( module ) // Transform, if we belong to a module { @@ -422,19 +424,27 @@ const EDA_RECT DRAWSEGMENT::GetBoundingBox() const pt += module->GetPosition(); } - if( ii == 0 ) + + if( first ) + { p_end = pt; + bbox.SetX( pt.x ); + bbox.SetY( pt.y ); + first = false; + } + else + { - bbox.SetX( std::min( bbox.GetX(), pt.x ) ); - bbox.SetY( std::min( bbox.GetY(), pt.y ) ); - p_end.x = std::max( p_end.x, pt.x ); - p_end.y = std::max( p_end.y, pt.y ); + bbox.SetX( std::min( bbox.GetX(), pt.x ) ); + bbox.SetY( std::min( bbox.GetY(), pt.y ) ); + + p_end.x = std::max( p_end.x, pt.x ); + p_end.y = std::max( p_end.y, pt.y ); + } } - bbox.SetEnd( p_end ); - } break; - + } default: break; } @@ -508,6 +518,18 @@ bool DRAWSEGMENT::HitTest( const wxPoint& aPosition ) const break; case S_POLYGON: // not yet handled + { + #define MAX_DIST_IN_MM 0.25 + int distmax = Millimeter2iu( 0.25 ); + SHAPE_POLY_SET::VERTEX_INDEX dummy; + auto poly = m_Poly; + + if( poly.CollideVertex( VECTOR2I( aPosition ), dummy, distmax ) ) + return true; + + if( poly.CollideEdge( VECTOR2I( aPosition ), dummy, distmax ) ) + return true; + } break; default: @@ -710,3 +732,26 @@ void DRAWSEGMENT::computeArcBBox( EDA_RECT& aBBox ) const angle -= 900; } } + +void DRAWSEGMENT::SetPolyPoints( const std::vector& aPoints ) +{ + m_Poly.RemoveAllContours(); + m_Poly.NewOutline(); + + for ( auto p : aPoints ) + { + m_Poly.Append( p.x, p.y ); + } +} + +const std::vector DRAWSEGMENT::GetPolyPoints() const +{ + std::vector rv; + + for ( auto iter = m_Poly.CIterate(); iter; iter++ ) + { + rv.push_back( wxPoint( iter->x, iter->y ) ); + } + + return rv; +} diff --git a/pcbnew/class_drawsegment.h b/pcbnew/class_drawsegment.h index 4ea38f5417..35bb464641 100644 --- a/pcbnew/class_drawsegment.h +++ b/pcbnew/class_drawsegment.h @@ -36,6 +36,7 @@ #include #include +#include class LINE_READER; class EDA_DRAW_FRAME; @@ -57,7 +58,7 @@ protected: wxPoint m_BezierC2; ///< Bezier Control Point 2 std::vector m_BezierPoints; - std::vector m_PolyPoints; + SHAPE_POLY_SET m_Poly; // Computes the bounding box for an arc void computeArcBBox( EDA_RECT& aBBox ) const; @@ -165,19 +166,18 @@ public: // Accessors: const std::vector& GetBezierPoints() const { return m_BezierPoints; } - const std::vector& GetPolyPoints() const { return m_PolyPoints; } - // same accessor, to add/change corners of the polygon - std::vector& GetPolyPoints() { return m_PolyPoints; } + + const std::vector GetPolyPoints() const; + SHAPE_POLY_SET& GetPolyShape() { return m_Poly; } + const SHAPE_POLY_SET& GetPolyShape() const { return m_Poly; } + void SetPolyShape( const SHAPE_POLY_SET& aShape ) { m_Poly = aShape; } void SetBezierPoints( const std::vector& aPoints ) { m_BezierPoints = aPoints; } - void SetPolyPoints( const std::vector& aPoints ) - { - m_PolyPoints = aPoints; - } + void SetPolyPoints( const std::vector& aPoints ); void Draw( EDA_DRAW_PANEL* panel, wxDC* DC, GR_DRAWMODE aDrawMode, const wxPoint& aOffset = ZeroOffset ) override; diff --git a/pcbnew/class_edge_mod.cpp b/pcbnew/class_edge_mod.cpp index b79305ab1a..268b7c53c8 100644 --- a/pcbnew/class_edge_mod.cpp +++ b/pcbnew/class_edge_mod.cpp @@ -202,7 +202,12 @@ void EDGE_MODULE::Draw( EDA_DRAW_PANEL* panel, wxDC* DC, GR_DRAWMODE draw_mode, { // We must compute absolute coordinates from m_PolyPoints // which are relative to module position, orientation 0 - std::vector points = m_PolyPoints; + std::vector points; + + for( auto iter = m_Poly.CIterate(); iter; iter++ ) + { + points.push_back( wxPoint( iter->x,iter->y ) ); + } for( unsigned ii = 0; ii < points.size(); ii++ ) { @@ -300,8 +305,11 @@ void EDGE_MODULE::Flip( const wxPoint& aCentre ) case S_POLYGON: // polygon corners coordinates are always relative to the // footprint position, orientation 0 - for( unsigned ii = 0; ii < m_PolyPoints.size(); ii++ ) - MIRROR( m_PolyPoints[ii].y, 0 ); + for( auto iter = m_Poly.Iterate(); iter; iter++ ) + { + MIRROR( iter->y, 0 ); + } + break; } // DRAWSEGMENT items are not usually on copper layers, but @@ -338,12 +346,12 @@ void EDGE_MODULE::Mirror( wxPoint aCentre, bool aMirrorAroundXAxis ) case S_POLYGON: // polygon corners coordinates are always relative to the // footprint position, orientation 0 - for( unsigned ii = 0; ii < m_PolyPoints.size(); ii++ ) + for( auto iter = m_Poly.Iterate(); iter; iter++ ) { if( aMirrorAroundXAxis ) - MIRROR( m_PolyPoints[ii].y, aCentre.y ); + MIRROR( iter->y, aCentre.y ); else - MIRROR( m_PolyPoints[ii].x, aCentre.x ); + MIRROR( iter->x, aCentre.x ); } } @@ -378,8 +386,8 @@ void EDGE_MODULE::Move( const wxPoint& aMoveVector ) case S_POLYGON: // polygon corners coordinates are always relative to the // footprint position, orientation 0 - for( unsigned ii = 0; ii < m_PolyPoints.size(); ii++ ) - m_PolyPoints[ii] += aMoveVector; + for( auto iter = m_Poly.Iterate(); iter; iter++ ) + *iter += VECTOR2I( aMoveVector ); } SetDrawCoord(); diff --git a/pcbnew/class_pad.h b/pcbnew/class_pad.h index c1ac8b0c68..08fd95d45c 100644 --- a/pcbnew/class_pad.h +++ b/pcbnew/class_pad.h @@ -272,13 +272,16 @@ public: * a filled circle or ring ( if thickness == 0, this is a filled circle, else a ring) * a arc */ - void AddPrimitive( std::vector& aPoly, int aThickness ); ///< add a polygonal basic shape + void AddPrimitive( const SHAPE_POLY_SET& aPoly, int aThickness ); ///< add a polygonal basic shape + void AddPrimitive( const std::vector& aPoly, int aThickness ); ///< add a polygonal basic shape void AddPrimitive( wxPoint aStart, wxPoint aEnd, int aThickness ); ///< segment basic shape void AddPrimitive( wxPoint aCenter, int aRadius, int aThickness ); ///< ring or circle basic shape void AddPrimitive( wxPoint aCenter, wxPoint aStart, int aArcAngle, int aThickness ); ///< arc basic shape + bool GetBestAnchorPosition( VECTOR2I& aPos ); + /** * Merge all basic shapes, converted to a polygon in one polygon, * in m_customShapeAsPolygon @@ -726,6 +729,9 @@ private: */ int boundingRadius() const; + bool buildCustomPadPolygon( SHAPE_POLY_SET* aMergedPolygon, + int aCircleToSegmentsCount ); + private: // Private variable members: // Actually computed and cached on demand by the accessor diff --git a/pcbnew/class_pad_custom_shape_functions.cpp b/pcbnew/class_pad_custom_shape_functions.cpp index 25a4a58457..34e4779f45 100644 --- a/pcbnew/class_pad_custom_shape_functions.cpp +++ b/pcbnew/class_pad_custom_shape_functions.cpp @@ -66,7 +66,18 @@ void PAD_CS_PRIMITIVE::ExportTo( DRAWSEGMENT* aTarget ) * add a free shape to the shape list. * the shape is a polygon (can be with thick outline), segment, circle or arc */ -void D_PAD::AddPrimitive( std::vector& aPoly, int aThickness ) + +void D_PAD::AddPrimitive( const SHAPE_POLY_SET& aPoly, int aThickness ) +{ + std::vector points; + + for( auto iter = aPoly.CIterate(); iter; iter++ ) + points.push_back( wxPoint( iter->x, iter->y ) ); + + AddPrimitive( points, aThickness ); +} + +void D_PAD::AddPrimitive( const std::vector& aPoly, int aThickness ) { PAD_CS_PRIMITIVE shape( S_POLYGON ); shape.m_Poly = aPoly; @@ -135,38 +146,10 @@ void D_PAD::DeletePrimitivesList() } -/* Merge all basic shapes, converted to a polygon in one polygon, - * return true if OK, false in there is more than one polygon - * in aMergedPolygon - */ -bool D_PAD::MergePrimitivesAsPolygon( SHAPE_POLY_SET* aMergedPolygon, - int aCircleToSegmentsCount ) +bool D_PAD::buildCustomPadPolygon( SHAPE_POLY_SET* aMergedPolygon, + int aCircleToSegmentsCount ) + { - // if aMergedPolygon == NULL, use m_customShapeAsPolygon as target - - if( !aMergedPolygon ) - aMergedPolygon = &m_customShapeAsPolygon; - - aMergedPolygon->RemoveAllContours(); - - // Add the anchor pad shape in aMergedPolygon, others in aux_polyset: - // The anchor pad is always at 0,0 - switch( GetAnchorPadShape() ) - { - default: - case PAD_SHAPE_CIRCLE: - TransformCircleToPolygon( *aMergedPolygon, wxPoint( 0,0 ), GetSize().x/2, - aCircleToSegmentsCount ); - break; - - case PAD_SHAPE_RECT: - { - SHAPE_RECT rect( -GetSize().x/2, -GetSize().y/2, GetSize().x, GetSize().y ); - aMergedPolygon->AddOutline( rect.Outline() ); - } - break; - } - SHAPE_POLY_SET aux_polyset; for( unsigned cnt = 0; cnt < m_basicShapes.size(); ++cnt ) @@ -212,7 +195,9 @@ bool D_PAD::MergePrimitivesAsPolygon( SHAPE_POLY_SET* aMergedPolygon, polyset.NewOutline(); for( unsigned ii = 0; ii < poly.size(); ii++ ) + { polyset.Append( poly[ii].x, poly[ii].y ); + } polyset.Inflate( bshape.m_Thickness/2, 32 ); @@ -230,13 +215,79 @@ bool D_PAD::MergePrimitivesAsPolygon( SHAPE_POLY_SET* aMergedPolygon, } } - // Merge all polygons: + aux_polyset.Simplify( SHAPE_POLY_SET::PM_FAST ); + + // Merge all polygons, if more than one, pick the largest (area-wise) if( aux_polyset.OutlineCount() ) { + + if( aux_polyset.OutlineCount() >= 2) + { + int bestOutline = 0; + double maxArea = 0.0; + + for( int i = 0; i < aux_polyset.OutlineCount(); i++ ) + { + double area = aux_polyset.COutline(i).Area(); + + if ( area > maxArea ) + { + maxArea = area; + bestOutline = i; + } + } + + if( bestOutline != 0 ) + aux_polyset.Polygon( 0 ) = aux_polyset.Polygon( bestOutline ); + + for (int i = 1; i < aux_polyset.OutlineCount(); i++ ) + { + aux_polyset.DeletePolygon( i ); + } + } + aMergedPolygon->BooleanAdd( aux_polyset, SHAPE_POLY_SET::PM_FAST ); aMergedPolygon->Fracture( SHAPE_POLY_SET::PM_FAST ); } + return aMergedPolygon->OutlineCount() <= 1; +} + +/* Merge all basic shapes, converted to a polygon in one polygon, + * return true if OK, false in there is more than one polygon + * in aMergedPolygon + */ +bool D_PAD::MergePrimitivesAsPolygon( SHAPE_POLY_SET* aMergedPolygon, + int aCircleToSegmentsCount ) +{ + // if aMergedPolygon == NULL, use m_customShapeAsPolygon as target + + if( !aMergedPolygon ) + aMergedPolygon = &m_customShapeAsPolygon; + + aMergedPolygon->RemoveAllContours(); + + // Add the anchor pad shape in aMergedPolygon, others in aux_polyset: + // The anchor pad is always at 0,0 + switch( GetAnchorPadShape() ) + { + default: + case PAD_SHAPE_CIRCLE: + TransformCircleToPolygon( *aMergedPolygon, wxPoint( 0,0 ), GetSize().x/2, + aCircleToSegmentsCount ); + break; + + case PAD_SHAPE_RECT: + { + SHAPE_RECT rect( -GetSize().x/2, -GetSize().y/2, GetSize().x, GetSize().y ); + aMergedPolygon->AddOutline( rect.Outline() ); + } + break; + } + + if ( !buildCustomPadPolygon( aMergedPolygon, aCircleToSegmentsCount ) ) + return false; + m_boundingRadius = -1; // The current bouding radius is no more valid. return aMergedPolygon->OutlineCount() <= 1; @@ -265,3 +316,79 @@ void D_PAD::CustomShapeAsPolygonToBoardPosition( SHAPE_POLY_SET * aMergedPolygon } } } + +bool D_PAD::GetBestAnchorPosition( VECTOR2I& aPos ) +{ + SHAPE_POLY_SET poly; + + if ( !buildCustomPadPolygon( &poly, 16 ) ) + return false; + + const int minSteps = 10; + const int maxSteps = 50; + + int stepsX, stepsY; + + auto bbox = poly.BBox(); + + if( bbox.GetWidth() < bbox.GetHeight() ) + { + stepsX = minSteps; + stepsY = minSteps * (double) bbox.GetHeight() / (double )(bbox.GetWidth() + 1); + } + else + { + stepsY = minSteps; + stepsX = minSteps * (double) bbox.GetWidth() / (double )(bbox.GetHeight() + 1); + } + + stepsX = std::max(minSteps, std::min( maxSteps, stepsX ) ); + stepsY = std::max(minSteps, std::min( maxSteps, stepsY ) ); + + auto center = bbox.Centre(); + + auto minDist = std::numeric_limits::max(); + int64_t minDistEdge; + + if( GetAnchorPadShape() == PAD_SHAPE_CIRCLE ) + { + minDistEdge = GetSize().x; + } + else + { + minDistEdge = std::max( GetSize().x, GetSize().y ); + } + + boost::optional bestAnchor; + + for ( int y = 0; y < stepsY ; y++ ) + for ( int x = 0; x < stepsX; x++ ) + { + VECTOR2I p = bbox.GetPosition(); + p.x += rescale( x, bbox.GetWidth(), (stepsX - 1) ); + p.y += rescale( y, bbox.GetHeight(), (stepsY - 1) ); + + if ( poly.Contains(p) ) + { + + auto dist = (center - p).EuclideanNorm(); + auto distEdge = poly.COutline(0).Distance( p, true ); + if ( distEdge >= minDistEdge ) + { + if ( dist < minDist ) + { + bestAnchor = p; + minDist = dist; + } + } + } + } + + if ( bestAnchor ) + { + aPos = *bestAnchor; + return true; + } + + return false; +} diff --git a/pcbnew/dialogs/dialog_graphic_item_properties.cpp b/pcbnew/dialogs/dialog_graphic_item_properties.cpp index 7452df69a4..8aa50a8cfa 100644 --- a/pcbnew/dialogs/dialog_graphic_item_properties.cpp +++ b/pcbnew/dialogs/dialog_graphic_item_properties.cpp @@ -312,6 +312,9 @@ bool DIALOG_GRAPHIC_ITEM_PROPERTIES::Validate() break; + case S_POLYGON: + break; + default: // Check start and end are not the same. diff --git a/pcbnew/dialogs/dialog_pad_properties.cpp b/pcbnew/dialogs/dialog_pad_properties.cpp index 04e6bc46fd..65b32b7d69 100644 --- a/pcbnew/dialogs/dialog_pad_properties.cpp +++ b/pcbnew/dialogs/dialog_pad_properties.cpp @@ -329,7 +329,7 @@ void DIALOG_PAD_PROPERTIES::OnPaintShowPanel( wxPaintEvent& event ) case S_POLYGON: // polygon { - std::vector& poly = dummySegment.GetPolyPoints(); + std::vector poly = dummySegment.GetPolyPoints(); GRClosedPoly( NULL, &dc, poly.size(), &poly[0], /* filled */ true, primitive.m_Thickness, hcolor, hcolor ); } @@ -1160,11 +1160,9 @@ void DIALOG_PAD_PROPERTIES::redraw() case S_POLYGON: // polygon { - std::vector& poly = dummySegment->GetPolyPoints(); - - for( unsigned ii = 0; ii < poly.size(); ii++ ) + for( auto iter = dummySegment->GetPolyShape().Iterate(); iter; iter++ ) { - poly[ii] += m_dummyPad->GetPosition(); + (*iter) += VECTOR2I( m_dummyPad->GetPosition() ); } } break; diff --git a/pcbnew/librairi.cpp b/pcbnew/librairi.cpp index 298bdd58f8..549fc34c51 100644 --- a/pcbnew/librairi.cpp +++ b/pcbnew/librairi.cpp @@ -253,7 +253,7 @@ MODULE* try_load_footprint( const wxFileName& aFileName, IO_MGR::PCB_FILE_T aFil } -MODULE* FOOTPRINT_EDIT_FRAME::Import_Module() +MODULE* FOOTPRINT_EDIT_FRAME::Import_Module( const wxString& aName ) { wxString lastOpenedPathForLoading = m_mruPath; wxConfigBase* config = Kiface().KifaceSettings(); @@ -261,7 +261,12 @@ MODULE* FOOTPRINT_EDIT_FRAME::Import_Module() if( config ) config->Read( EXPORT_IMPORT_LASTPATH_KEY, &lastOpenedPathForLoading ); - wxFileName fn = getFootprintFilenameFromUser( this, lastOpenedPathForLoading ); + wxFileName fn; + + if( aName != wxT("") ) + fn = aName; + else + fn = getFootprintFilenameFromUser( this, lastOpenedPathForLoading ); if( !fn.IsOk() ) return NULL; diff --git a/pcbnew/microwave.cpp b/pcbnew/microwave.cpp index ed5c1faee2..0b2e62a124 100644 --- a/pcbnew/microwave.cpp +++ b/pcbnew/microwave.cpp @@ -634,7 +634,7 @@ MODULE* PCB_EDIT_FRAME::Create_MuWavePolygonShape() module->GraphicalItemsList().PushFront( edge ); // Get the corner buffer of the polygonal edge - std::vector& polyPoints = edge->GetPolyPoints(); + std::vector polyPoints; polyPoints.reserve( PolyEdges.size() + 2 ); // Init start point coord: @@ -670,6 +670,7 @@ MODULE* PCB_EDIT_FRAME::Create_MuWavePolygonShape() break; } + edge->SetPolyPoints( polyPoints ); PolyEdges.clear(); module->CalculateBoundingBox(); GetBoard()->m_Status_Pcb = 0; diff --git a/pcbnew/tools/edit_tool.cpp b/pcbnew/tools/edit_tool.cpp index f975950abc..bb6fe3f807 100644 --- a/pcbnew/tools/edit_tool.cpp +++ b/pcbnew/tools/edit_tool.cpp @@ -295,12 +295,13 @@ bool EDIT_TOOL::Init() menu.AddItem( PCB_ACTIONS::properties, SELECTION_CONDITIONS::Count( 1 ) || SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) ); + menu.AddItem( PCB_ACTIONS::moveExact, SELECTION_CONDITIONS::NotEmpty ); menu.AddItem( PCB_ACTIONS::positionRelative, SELECTION_CONDITIONS::NotEmpty ); menu.AddItem( PCB_ACTIONS::duplicate, SELECTION_CONDITIONS::NotEmpty ); menu.AddItem( PCB_ACTIONS::createArray, SELECTION_CONDITIONS::NotEmpty ); - menu.AddSeparator(); + menu.AddItem( PCB_ACTIONS::copyToClipboard, SELECTION_CONDITIONS::NotEmpty ); menu.AddItem( PCB_ACTIONS::cutToClipboard, SELECTION_CONDITIONS::NotEmpty ); menu.AddItem( PCB_ACTIONS::pasteFromClipboard ); @@ -308,6 +309,8 @@ bool EDIT_TOOL::Init() // Mirror only available in modedit menu.AddItem( PCB_ACTIONS::mirror, editingModuleCondition && SELECTION_CONDITIONS::NotEmpty ); + menu.AddItem( PCB_ACTIONS::createPadFromShapes, editingModuleCondition && SELECTION_CONDITIONS::NotEmpty ); + menu.AddItem( PCB_ACTIONS::explodePadToShapes, editingModuleCondition && SELECTION_CONDITIONS::NotEmpty ); // Footprint actions menu.AddItem( PCB_ACTIONS::editFootprintInFpEditor, diff --git a/pcbnew/tools/module_editor_tools.cpp b/pcbnew/tools/module_editor_tools.cpp index 37548cd9d3..31777ef61f 100644 --- a/pcbnew/tools/module_editor_tools.cpp +++ b/pcbnew/tools/module_editor_tools.cpp @@ -59,6 +59,14 @@ TOOL_ACTION PCB_ACTIONS::placePad( "pcbnew.ModuleEditor.placePad", AS_GLOBAL, 0, _( "Add Pad" ), _( "Add a pad" ), NULL, AF_ACTIVATE ); +TOOL_ACTION PCB_ACTIONS::createPadFromShapes( "pcbnew.ModuleEditor.createPadFromShapes", + AS_CONTEXT, 0, + _( "Create Pad from Selected Shapes" ), _( "Creates a custom-shaped pads from a set of selected shapes" ) ); + +TOOL_ACTION PCB_ACTIONS::explodePadToShapes( "pcbnew.ModuleEditor.explodePadToShapes", + AS_CONTEXT, 0, + _( "Explode Selected Pad to Graphical Shapes" ), _( "Converts a custom-shaped pads to a set of graphical shapes" ) ); + TOOL_ACTION PCB_ACTIONS::enumeratePads( "pcbnew.ModuleEditor.enumeratePads", AS_GLOBAL, 0, _( "Enumerate Pads" ), _( "Enumerate pads" ), pad_enumerate_xpm, AF_ACTIVATE ); @@ -321,10 +329,209 @@ int MODULE_EDITOR_TOOLS::ModuleEdgeOutlines( const TOOL_EVENT& aEvent ) return 0; } +int MODULE_EDITOR_TOOLS::ExplodePadToShapes( const TOOL_EVENT& aEvent ) +{ + SELECTION& selection = m_toolMgr->GetTool()->GetSelection(); + BOARD_COMMIT commit( frame() ); + + if( selection.Size() != 1 ) + return 0; + + if( selection[0]->Type() != PCB_PAD_T ) + return 0; + + auto pad = static_cast( selection[0] ); + + if( pad->GetShape() != PAD_SHAPE_CUSTOM ) + return 0; + + commit.Modify( pad ); + + wxPoint anchor = pad->GetPosition(); + + for( auto prim : pad->GetPrimitives() ) + { + auto ds = new EDGE_MODULE( board()->m_Modules ); + + ds->SetLayer( pad->GetLayer() ); + ds->SetShape( prim.m_Shape ); + ds->SetStart( prim.m_Start + anchor ); + ds->SetEnd( prim.m_End + anchor ); + ds->SetWidth( prim.m_Thickness ); + + for( auto&p : prim.m_Poly ) + p += anchor; + + ds->SetPolyPoints( prim.m_Poly ); + ds->SetAngle( prim.m_ArcAngle ); + + commit.Add( ds ); + } + + pad->SetShape( pad->GetAnchorPadShape() ); + commit.Push( _("Explode pad to shapes") ); + + m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true ); + + return 0; +} + + +int MODULE_EDITOR_TOOLS::CreatePadFromShapes( const TOOL_EVENT& aEvent ) +{ + SELECTION& selection = m_toolMgr->GetTool()->GetSelection(); + + std::unique_ptr pad ( new D_PAD ( board()->m_Modules ) ); + D_PAD *refPad = nullptr; + bool multipleRefPadsFound = false; + bool illegalItemsFound = false; + + std::vector shapes; + + BOARD_COMMIT commit( frame() ); + + for( auto item : selection ) + { + switch( item->Type() ) + { + case PCB_PAD_T: + { + if( refPad ) + multipleRefPadsFound = true; + + refPad = static_cast( item ); + break; + } + + case PCB_MODULE_EDGE_T: + { + auto em = static_cast ( item ); + + PAD_CS_PRIMITIVE shape( em->GetShape() ); + shape.m_Start = em->GetStart(); + shape.m_End = em->GetEnd(); + shape.m_Radius = em->GetRadius(); + shape.m_Thickness = em->GetWidth(); + shape.m_ArcAngle = em->GetAngle(); + + for ( auto p : em->GetPolyPoints() ) + shape.m_Poly.push_back(p); + + shapes.push_back(shape); + + break; + } + + default: + { + illegalItemsFound = true; + break; + } + } + } + + if( refPad && selection.Size() == 1 ) + { + // don't convert a pad into itself... + return 0; + } + + if( multipleRefPadsFound ) + { + DisplayErrorMessage( frame(), _("Cannot convert items to a custom-shaped pad: selection contains more than one reference pad. ") ); + return 0; + } + + if( illegalItemsFound ) + { + DisplayErrorMessage( frame(), _("Cannot convert items to a custom-shaped pad: selection contains unsupported items. Only graphical lines, circles, arcs and polygons are allowed.") ); + return 0; + } + + if( refPad ) + { + pad.reset( static_cast( refPad->Clone() ) ); + } + else + { + pad->SetAnchorPadShape( PAD_SHAPE_CIRCLE ); + pad->SetAttribute( PAD_ATTRIB_SMD ); + pad->SetLayerSet( D_PAD::SMDMask() ); + pad->SetSize ( wxSize( 10000, 10000 ) ); + pad->IncrementPadName( true, true ); + } + + + pad->SetPrimitives( shapes ); + pad->SetShape ( PAD_SHAPE_CUSTOM ); + + boost::optional anchor; + VECTOR2I tmp; + + if( refPad ) + { + anchor = pad->GetPosition(); + } + else if( pad->GetBestAnchorPosition( tmp ) ) + { + anchor = tmp; + } + + if( !anchor ) + { + DisplayErrorMessage( frame(), _("Cannot convert items to a custom-shaped pad: unable to determine the anchor point position. Consider adding a small anchor pad to the selection and try again.") ); + return 0; + } + + + // relocate the shapes, they are relative to the anchor pad position + for( auto& shape : shapes ) + { + shape.m_Start.x -= anchor->x; + shape.m_Start.y -= anchor->y; + shape.m_End.x -= anchor->x; + shape.m_End.y -= anchor->y; + + for( auto&p : shape.m_Poly ) + { + p.x -= anchor->x; + p.y -= anchor->y; + } + } + + + pad->SetPosition( wxPoint( anchor->x, anchor->y ) ); + pad->SetPrimitives( shapes ); + + bool result = pad->MergePrimitivesAsPolygon(); + + if( !result ) + { + DisplayErrorMessage( frame(), _("Cannot convert items to a custom-shaped pad: selected items do not form a single solid shape.") ); + return 0; + } + + auto padPtr = pad.release(); + + commit.Add( padPtr ); + for ( auto item : selection ) + { + commit.Remove( item ); + } + + m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true ); + m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, padPtr ); + + commit.Push(_("Create Pad From Selected Shapes") ); + + return 0; +} void MODULE_EDITOR_TOOLS::setTransitions() { Go( &MODULE_EDITOR_TOOLS::PlacePad, PCB_ACTIONS::placePad.MakeEvent() ); + Go( &MODULE_EDITOR_TOOLS::CreatePadFromShapes, PCB_ACTIONS::createPadFromShapes.MakeEvent() ); + Go( &MODULE_EDITOR_TOOLS::ExplodePadToShapes, PCB_ACTIONS::explodePadToShapes.MakeEvent() ); Go( &MODULE_EDITOR_TOOLS::EnumeratePads, PCB_ACTIONS::enumeratePads.MakeEvent() ); Go( &MODULE_EDITOR_TOOLS::ModuleTextOutlines, PCB_ACTIONS::moduleTextOutlines.MakeEvent() ); Go( &MODULE_EDITOR_TOOLS::ModuleEdgeOutlines, PCB_ACTIONS::moduleEdgeOutlines.MakeEvent() ); diff --git a/pcbnew/tools/module_editor_tools.h b/pcbnew/tools/module_editor_tools.h index 37ba71a66c..3524fc1935 100644 --- a/pcbnew/tools/module_editor_tools.h +++ b/pcbnew/tools/module_editor_tools.h @@ -82,6 +82,21 @@ public: */ int ModuleEdgeOutlines( const TOOL_EVENT& aEvent ); + /** + * Function CreatePadFromShapes() + * + * Creates a custom-shaped pad from a set of selected graphical shapes + */ + int CreatePadFromShapes( const TOOL_EVENT& aEvent ); + + /** + * Function ExplodePadToShapes() + * + * Breaks apart a complex-shaped part into a set of graphical shapes + */ + int ExplodePadToShapes( const TOOL_EVENT& aEvent ); + + ///> Sets up handlers for various events. void setTransitions() override; diff --git a/pcbnew/tools/pad_tool.cpp b/pcbnew/tools/pad_tool.cpp index 7c67095449..e240c02666 100644 --- a/pcbnew/tools/pad_tool.cpp +++ b/pcbnew/tools/pad_tool.cpp @@ -80,6 +80,7 @@ public: Add( PCB_ACTIONS::applyPadSettings ); Add( PCB_ACTIONS::pushPadSettings ); + // show modedit-specific items if( m_editingFootprint ) {