From 8165fc6c4468883d332c6c9f21f80a91a8d83d63 Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Mon, 28 Nov 2022 20:46:30 +0000 Subject: [PATCH] Give up trying to infer what kind of polygon the user wants. In many instances there are 3 valid answers: mimic linewidths, use centerlines, and build a bounding hull. Fixes https://gitlab.com/kicad/code/kicad/issues/12950 --- pcbnew/dialogs/dialog_copper_zones.cpp | 35 ++- .../dialog_non_copper_zones_properties.cpp | 35 ++- .../dialogs/dialog_rule_area_properties.cpp | 37 +-- pcbnew/pcbnew_settings.h | 13 +- pcbnew/tools/convert_tool.cpp | 214 +++++++++++------- pcbnew/tools/convert_tool.h | 10 +- 6 files changed, 219 insertions(+), 125 deletions(-) diff --git a/pcbnew/dialogs/dialog_copper_zones.cpp b/pcbnew/dialogs/dialog_copper_zones.cpp index 1f4aff0168..d222480e05 100644 --- a/pcbnew/dialogs/dialog_copper_zones.cpp +++ b/pcbnew/dialogs/dialog_copper_zones.cpp @@ -23,6 +23,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ +#include #include #include #include @@ -124,7 +125,8 @@ private: std::vector m_netInfoItemList; CONVERT_SETTINGS* m_convertSettings; - wxCheckBox* m_cbIgnoreLineWidths; + wxRadioButton* m_rbCenterline; + wxRadioButton* m_rbEnvelope; wxCheckBox* m_cbDeleteOriginals; }; @@ -184,7 +186,8 @@ DIALOG_COPPER_ZONE::DIALOG_COPPER_ZONE( PCB_BASE_FRAME* aParent, ZONE_SETTINGS* m_islandThresholdUnits ), m_hideAutoGeneratedNets{ false }, m_convertSettings( aConvertSettings ), - m_cbIgnoreLineWidths( nullptr ), + m_rbCenterline( nullptr ), + m_rbEnvelope( nullptr ), m_cbDeleteOriginals( nullptr ) { m_Parent = aParent; @@ -217,16 +220,18 @@ DIALOG_COPPER_ZONE::DIALOG_COPPER_ZONE( PCB_BASE_FRAME* aParent, ZONE_SETTINGS* if( aConvertSettings ) { - wxStaticBox* bConvertBox = new wxStaticBox( this, wxID_ANY, - _( "Conversion Settings" ) ); + wxStaticBox* bConvertBox = new wxStaticBox( this, wxID_ANY, _( "Conversion Settings" ) ); wxStaticBoxSizer* bConvertSizer = new wxStaticBoxSizer( bConvertBox, wxVERTICAL ); - m_cbIgnoreLineWidths = new wxCheckBox( this, wxID_ANY, - _( "Ignore source object line widths" ) ); - bConvertSizer->Add( m_cbIgnoreLineWidths, 0, wxLEFT|wxRIGHT, 5 ); + m_rbCenterline = new wxRadioButton( this, wxID_ANY, _( "Use centerlines" ) ); + bConvertSizer->Add( m_rbCenterline, 0, wxLEFT|wxRIGHT, 5 ); - m_cbDeleteOriginals = new wxCheckBox( this, wxID_ANY, - _( "Delete source objects after conversion" ) ); + bConvertSizer->AddSpacer( 2 ); + m_rbEnvelope = new wxRadioButton( this, wxID_ANY, _( "Create bounding hull" ) ); + bConvertSizer->Add( m_rbEnvelope, 0, wxLEFT|wxRIGHT, 5 ); + + bConvertSizer->AddSpacer( 6 ); + m_cbDeleteOriginals = new wxCheckBox( this, wxID_ANY, _( "Delete source objects after conversion" ) ); bConvertSizer->Add( m_cbDeleteOriginals, 0, wxALL, 5 ); GetSizer()->Insert( 0, bConvertSizer, 0, wxALL|wxEXPAND, 10 ); @@ -263,7 +268,11 @@ bool DIALOG_COPPER_ZONE::TransferDataToWindow() { if( m_convertSettings ) { - m_cbIgnoreLineWidths->SetValue( m_convertSettings->m_IgnoreLineWidths ); + if( m_convertSettings->m_Strategy == BOUNDING_HULL ) + m_rbEnvelope->SetValue( true ); + else + m_rbCenterline->SetValue( true ); + m_cbDeleteOriginals->SetValue( m_convertSettings->m_DeleteOriginals ); } @@ -479,7 +488,11 @@ bool DIALOG_COPPER_ZONE::TransferDataFromWindow() if( m_convertSettings ) { - m_convertSettings->m_IgnoreLineWidths = m_cbIgnoreLineWidths->GetValue(); + if( m_rbEnvelope->GetValue() ) + m_convertSettings->m_Strategy = BOUNDING_HULL; + else + m_convertSettings->m_Strategy = CENTERLINE; + m_convertSettings->m_DeleteOriginals = m_cbDeleteOriginals->GetValue(); } diff --git a/pcbnew/dialogs/dialog_non_copper_zones_properties.cpp b/pcbnew/dialogs/dialog_non_copper_zones_properties.cpp index c06bbe01af..bb18f81dd7 100644 --- a/pcbnew/dialogs/dialog_non_copper_zones_properties.cpp +++ b/pcbnew/dialogs/dialog_non_copper_zones_properties.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -61,7 +62,8 @@ private: UNIT_BINDER m_cornerRadius; CONVERT_SETTINGS* m_convertSettings; - wxCheckBox* m_cbIgnoreLineWidths; + wxRadioButton* m_rbCenterline; + wxRadioButton* m_rbEnvelope; wxCheckBox* m_cbDeleteOriginals; }; @@ -89,7 +91,8 @@ DIALOG_NON_COPPER_ZONES_EDITOR::DIALOG_NON_COPPER_ZONES_EDITOR( PCB_BASE_FRAME* m_cornerSmoothingType( ZONE_SETTINGS::SMOOTHING_UNDEFINED ), m_cornerRadius( aParent, m_cornerRadiusLabel, m_cornerRadiusCtrl, m_cornerRadiusUnits ), m_convertSettings( aConvertSettings ), - m_cbIgnoreLineWidths( nullptr ), + m_rbCenterline( nullptr ), + m_rbEnvelope( nullptr ), m_cbDeleteOriginals( nullptr ) { m_parent = aParent; @@ -99,16 +102,18 @@ DIALOG_NON_COPPER_ZONES_EDITOR::DIALOG_NON_COPPER_ZONES_EDITOR( PCB_BASE_FRAME* if( aConvertSettings ) { - wxStaticBox* bConvertBox = new wxStaticBox( this, wxID_ANY, - _( "Conversion Settings" ) ); + wxStaticBox* bConvertBox = new wxStaticBox( this, wxID_ANY, _( "Conversion Settings" ) ); wxStaticBoxSizer* bConvertSizer = new wxStaticBoxSizer( bConvertBox, wxVERTICAL ); - m_cbIgnoreLineWidths = new wxCheckBox( this, wxID_ANY, - _( "Ignore source object line widths" ) ); - bConvertSizer->Add( m_cbIgnoreLineWidths, 0, wxLEFT|wxRIGHT, 5 ); + m_rbCenterline = new wxRadioButton( this, wxID_ANY, _( "Use centerlines" ) ); + bConvertSizer->Add( m_rbCenterline, 0, wxLEFT|wxRIGHT, 5 ); - m_cbDeleteOriginals = new wxCheckBox( this, wxID_ANY, - _( "Delete source objects after conversion" ) ); + bConvertSizer->AddSpacer( 2 ); + m_rbEnvelope = new wxRadioButton( this, wxID_ANY, _( "Create bounding hull" ) ); + bConvertSizer->Add( m_rbEnvelope, 0, wxLEFT|wxRIGHT, 5 ); + + bConvertSizer->AddSpacer( 6 ); + m_cbDeleteOriginals = new wxCheckBox( this, wxID_ANY, _( "Delete source objects after conversion" ) ); bConvertSizer->Add( m_cbDeleteOriginals, 0, wxALL, 5 ); GetSizer()->Insert( 0, bConvertSizer, 0, wxALL|wxEXPAND, 10 ); @@ -152,7 +157,11 @@ bool DIALOG_NON_COPPER_ZONES_EDITOR::TransferDataToWindow() { if( m_convertSettings ) { - m_cbIgnoreLineWidths->SetValue( m_convertSettings->m_IgnoreLineWidths ); + if( m_convertSettings->m_Strategy == BOUNDING_HULL ) + m_rbEnvelope->SetValue( true ); + else + m_rbCenterline->SetValue( true ); + m_cbDeleteOriginals->SetValue( m_convertSettings->m_DeleteOriginals ); } @@ -244,7 +253,11 @@ bool DIALOG_NON_COPPER_ZONES_EDITOR::TransferDataFromWindow() { if( m_convertSettings ) { - m_convertSettings->m_IgnoreLineWidths = m_cbIgnoreLineWidths->GetValue(); + if( m_rbEnvelope->GetValue() ) + m_convertSettings->m_Strategy = BOUNDING_HULL; + else + m_convertSettings->m_Strategy = CENTERLINE; + m_convertSettings->m_DeleteOriginals = m_cbDeleteOriginals->GetValue(); } diff --git a/pcbnew/dialogs/dialog_rule_area_properties.cpp b/pcbnew/dialogs/dialog_rule_area_properties.cpp index 3d08bcd2ac..6513a58739 100644 --- a/pcbnew/dialogs/dialog_rule_area_properties.cpp +++ b/pcbnew/dialogs/dialog_rule_area_properties.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #define LAYER_LIST_COLUMN_CHECK 0 #define LAYER_LIST_COLUMN_ICON 1 @@ -58,7 +59,8 @@ private: bool m_isFpEditor; CONVERT_SETTINGS* m_convertSettings; - wxCheckBox* m_cbIgnoreLineWidths; + wxRadioButton* m_rbCenterline; + wxRadioButton* m_rbEnvelope; wxCheckBox* m_cbDeleteOriginals; }; @@ -79,7 +81,8 @@ DIALOG_RULE_AREA_PROPERTIES::DIALOG_RULE_AREA_PROPERTIES( PCB_BASE_FRAME* aParen m_outlineHatchPitch( aParent, m_stBorderHatchPitchText, m_outlineHatchPitchCtrl, m_outlineHatchUnits ), m_convertSettings( aConvertSettings ), - m_cbIgnoreLineWidths( nullptr ), + m_rbCenterline( nullptr ), + m_rbEnvelope( nullptr ), m_cbDeleteOriginals( nullptr ) { m_parent = aParent; @@ -89,18 +92,18 @@ DIALOG_RULE_AREA_PROPERTIES::DIALOG_RULE_AREA_PROPERTIES( PCB_BASE_FRAME* aParen if( aConvertSettings ) { - wxBoxSizer* bConvertSizer = new wxBoxSizer( wxVERTICAL ); + wxStaticBox* bConvertBox = new wxStaticBox( this, wxID_ANY, _( "Conversion Settings" ) ); + wxStaticBoxSizer* bConvertSizer = new wxStaticBoxSizer( bConvertBox, wxVERTICAL ); - wxStaticText* conversionSettingsLabel = new wxStaticText( this, wxID_ANY, - _( "Conversion settings:" ) ); - bConvertSizer->Add( conversionSettingsLabel, 0, wxLEFT|wxRIGHT|wxEXPAND, 5 ); + m_rbCenterline = new wxRadioButton( this, wxID_ANY, _( "Use centerlines" ) ); + bConvertSizer->Add( m_rbCenterline, 0, wxLEFT|wxRIGHT, 5 ); - m_cbIgnoreLineWidths = new wxCheckBox( this, wxID_ANY, - _( "Ignore source object line widths" ) ); - bConvertSizer->Add( m_cbIgnoreLineWidths, 0, wxLEFT|wxRIGHT|wxTOP, 5 ); + bConvertSizer->AddSpacer( 2 ); + m_rbEnvelope = new wxRadioButton( this, wxID_ANY, _( "Create bounding hull" ) ); + bConvertSizer->Add( m_rbEnvelope, 0, wxLEFT|wxRIGHT, 5 ); - m_cbDeleteOriginals = new wxCheckBox( this, wxID_ANY, - _( "Delete source objects after conversion" ) ); + bConvertSizer->AddSpacer( 6 ); + m_cbDeleteOriginals = new wxCheckBox( this, wxID_ANY, _( "Delete source objects after conversion" ) ); bConvertSizer->Add( m_cbDeleteOriginals, 0, wxALL, 5 ); GetSizer()->Insert( 0, bConvertSizer, 0, wxALL|wxEXPAND, 10 ); @@ -129,7 +132,11 @@ bool DIALOG_RULE_AREA_PROPERTIES::TransferDataToWindow() { if( m_convertSettings ) { - m_cbIgnoreLineWidths->SetValue( m_convertSettings->m_IgnoreLineWidths ); + if( m_convertSettings->m_Strategy == BOUNDING_HULL ) + m_rbEnvelope->SetValue( true ); + else + m_rbCenterline->SetValue( true ); + m_cbDeleteOriginals->SetValue( m_convertSettings->m_DeleteOriginals ); } @@ -190,7 +197,11 @@ bool DIALOG_RULE_AREA_PROPERTIES::TransferDataFromWindow() { if( m_convertSettings ) { - m_convertSettings->m_IgnoreLineWidths = m_cbIgnoreLineWidths->GetValue(); + if( m_rbEnvelope->GetValue() ) + m_convertSettings->m_Strategy = BOUNDING_HULL; + else + m_convertSettings->m_Strategy = CENTERLINE; + m_convertSettings->m_DeleteOriginals = m_cbDeleteOriginals->GetValue(); } diff --git a/pcbnew/pcbnew_settings.h b/pcbnew/pcbnew_settings.h index bd1105bba9..c89919b533 100644 --- a/pcbnew/pcbnew_settings.h +++ b/pcbnew/pcbnew_settings.h @@ -32,11 +32,18 @@ namespace PNS // Settings for the CONVERT_TOOL. +enum CONVERT_STRATEGY +{ + COPY_LINEWIDTH, + CENTERLINE, + BOUNDING_HULL +}; + + struct CONVERT_SETTINGS { - bool m_StrokeHulls; - bool m_IgnoreLineWidths; - bool m_DeleteOriginals; + CONVERT_STRATEGY m_Strategy; + bool m_DeleteOriginals; }; diff --git a/pcbnew/tools/convert_tool.cpp b/pcbnew/tools/convert_tool.cpp index 5482465a20..31e6947f7f 100644 --- a/pcbnew/tools/convert_tool.cpp +++ b/pcbnew/tools/convert_tool.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -58,19 +59,28 @@ public: DIALOG_SHIM( aParent, wxID_ANY, _( "Conversion Settings" ), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ), m_settings( aSettings ), - m_cbIgnoreLineWidths( nullptr ), + m_rbMimicLineWidth( nullptr ), + m_rbCenterline( nullptr ), + m_rbEnvelope( nullptr ), m_cbDeleteOriginals( nullptr ) { wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL ); wxBoxSizer* topSizer = new wxBoxSizer( wxVERTICAL ); SetSizer( mainSizer ); - m_cbIgnoreLineWidths = new wxCheckBox( this, wxID_ANY, - _( "Ignore source object line widths" ) ); - topSizer->Add( m_cbIgnoreLineWidths, 0, wxLEFT|wxRIGHT, 5 ); + m_rbMimicLineWidth = new wxRadioButton( this, wxID_ANY, _( "Copy line width of first object" ) ); + topSizer->Add( m_rbMimicLineWidth, 0, wxLEFT|wxRIGHT, 5 ); - m_cbDeleteOriginals = new wxCheckBox( this, wxID_ANY, - _( "Delete source objects after conversion" ) ); + topSizer->AddSpacer( 2 ); + m_rbCenterline = new wxRadioButton( this, wxID_ANY, _( "Use centerlines" ) ); + topSizer->Add( m_rbCenterline, 0, wxLEFT|wxRIGHT, 5 ); + + topSizer->AddSpacer( 2 ); + m_rbEnvelope = new wxRadioButton( this, wxID_ANY, _( "Create bounding hull" ) ); + topSizer->Add( m_rbEnvelope, 0, wxLEFT|wxRIGHT, 5 ); + + topSizer->AddSpacer( 8 ); + m_cbDeleteOriginals = new wxCheckBox( this, wxID_ANY, _( "Delete source objects after conversion" ) ); topSizer->Add( m_cbDeleteOriginals, 0, wxALL, 5 ); wxStaticLine* line = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, @@ -103,21 +113,36 @@ public: protected: bool TransferDataToWindow() override { - m_cbIgnoreLineWidths->SetValue( m_settings->m_IgnoreLineWidths ); + switch( m_settings->m_Strategy ) + { + case COPY_LINEWIDTH: m_rbMimicLineWidth->SetValue( true ); break; + case CENTERLINE: m_rbCenterline->SetValue( true ); break; + case BOUNDING_HULL: m_rbEnvelope->SetValue( true ); break; + } + m_cbDeleteOriginals->SetValue( m_settings->m_DeleteOriginals ); return true; } bool TransferDataFromWindow() override { - m_settings->m_IgnoreLineWidths = m_cbIgnoreLineWidths->GetValue(); + if( m_rbEnvelope->GetValue() ) + m_settings->m_Strategy = BOUNDING_HULL; + else if( m_rbCenterline->GetValue() ) + m_settings->m_Strategy = CENTERLINE; + else + m_settings->m_Strategy = COPY_LINEWIDTH; + m_settings->m_DeleteOriginals = m_cbDeleteOriginals->GetValue(); return true; } private: CONVERT_SETTINGS* m_settings; - wxCheckBox* m_cbIgnoreLineWidths; + + wxRadioButton* m_rbMimicLineWidth; + wxRadioButton* m_rbCenterline; + wxRadioButton* m_rbEnvelope; wxCheckBox* m_cbDeleteOriginals; }; @@ -203,7 +228,9 @@ int CONVERT_TOOL::CreatePolys( const TOOL_EVENT& aEvent ) CONVERT_SETTINGS convertSettings; PCB_LAYER_ID destLayer = m_frame->GetActiveLayer(); FOOTPRINT* parentFootprint = nullptr; - bool foundChainedSegs = false; + + convertSettings.m_Strategy = BOUNDING_HULL; // Required for preflight at least + convertSettings.m_DeleteOriginals = true; PCB_SELECTION& selection = m_selectionTool->RequestSelection( []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool ) @@ -222,25 +249,15 @@ int CONVERT_TOOL::CreatePolys( const TOOL_EVENT& aEvent ) item->ClearTempFlags(); SHAPE_POLY_SET polySet; - SHAPE_POLY_SET temp; polySet.Append( makePolysFromClosedGraphics( selection.GetItems(), - convertSettings.m_IgnoreLineWidths ) ); + convertSettings.m_Strategy) ); - temp = makePolysFromChainedSegs( selection.GetItems() ); + polySet.Append( makePolysFromChainedSegs( selection.GetItems(), + convertSettings.m_Strategy) ); - if( !temp.IsEmpty() ) - { - polySet.Append( temp ); - foundChainedSegs = true; - } - else - { - for( EDA_ITEM* item : selection ) - item->ClearTempFlags(); - } - - polySet.Append( makePolysFromOpenGraphics( selection.GetItems() ) ); + if( convertSettings.m_Strategy == BOUNDING_HULL ) + polySet.Append( makePolysFromOpenGraphics( selection.GetItems() ) ); if( polySet.IsEmpty() ) return false; @@ -258,18 +275,10 @@ int CONVERT_TOOL::CreatePolys( const TOOL_EVENT& aEvent ) return true; }; - // Pre-flight getPolys(). If we find any chained segments then we default m_IgnoreLineWidths - // to true. - // We also use the pre-flight to keep from putting up any of the dialogs if there's nothing - // to convert. - convertSettings.m_IgnoreLineWidths = false; - convertSettings.m_DeleteOriginals = true; - + // Pre-flight getPolys() to see if there's anything to convert. if( !getPolys() ) return 0; - convertSettings.m_IgnoreLineWidths = foundChainedSegs; - bool isFootprint = m_frame->IsType( FRAME_FOOTPRINT_EDITOR ); if( isFootprint ) @@ -282,7 +291,9 @@ int CONVERT_TOOL::CreatePolys( const TOOL_EVENT& aEvent ) wxFAIL_MSG( wxT( "Unimplemented footprint parent in CONVERT_TOOL::CreatePolys" ) ); } - BOARD_COMMIT commit( m_frame ); + BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings(); + PCB_LAYER_ID layer = m_frame->GetActiveLayer(); + BOARD_COMMIT commit( m_frame ); if( aEvent.IsAction( &PCB_ACTIONS::convertToPoly ) ) { @@ -297,9 +308,36 @@ int CONVERT_TOOL::CreatePolys( const TOOL_EVENT& aEvent ) for( const SHAPE_POLY_SET& poly : polys ) { PCB_SHAPE* graphic = isFootprint ? new FP_SHAPE( parentFootprint ) : new PCB_SHAPE; + int width = bds.m_LineThickness[ bds.GetLayerClass( layer ) ]; + + if( convertSettings.m_Strategy == COPY_LINEWIDTH ) + { + BOARD_ITEM* topLeftItem = nullptr; + VECTOR2I pos; + + for( EDA_ITEM* item : selection ) + { + if( BOARD_ITEM* candidate = dynamic_cast( item ) ) + { + if( candidate->HasLineStroke() ) + { + pos = candidate->GetPosition(); + + if( !topLeftItem + || ( pos.x < topLeftItem->GetPosition().x ) + || ( topLeftItem->GetPosition().x == pos.x + && pos.y < topLeftItem->GetPosition().y ) ) + { + topLeftItem = candidate; + width = topLeftItem->GetStroke().GetWidth(); + } + } + } + } + } graphic->SetShape( SHAPE_T::POLY ); - graphic->SetStroke( STROKE_PARAMS( 0, PLOT_DASH_TYPE::SOLID, COLOR4D::UNSPECIFIED ) ); + graphic->SetStroke( STROKE_PARAMS( width, PLOT_DASH_TYPE::SOLID, COLOR4D::UNSPECIFIED ) ); graphic->SetLayer( destLayer ); graphic->SetPolyShape( poly ); @@ -375,25 +413,26 @@ int CONVERT_TOOL::CreatePolys( const TOOL_EVENT& aEvent ) } -SHAPE_POLY_SET CONVERT_TOOL::makePolysFromChainedSegs( const std::deque& aItems ) +SHAPE_POLY_SET CONVERT_TOOL::makePolysFromChainedSegs( const std::deque& aItems, + CONVERT_STRATEGY aStrategy ) { // TODO: This code has a somewhat-similar purpose to ConvertOutlineToPolygon but is slightly // different, so this remains a separate algorithm. It might be nice to analyze the dfiferences // in requirements and refactor this. - // Very tight epsilon used here to account for rounding errors in import, not sloppy drawing - const int chainingEpsilonSquared = SEG::Square( 100 ); + int chainingEpsilon = pcbIUScale.mmToIU( 0.02 ); // max dist from one endPt to next startPt - SHAPE_POLY_SET poly; + BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings(); + SHAPE_POLY_SET poly; // Stores pairs of (anchor, item) where anchor == 0 -> SEG.A, anchor == 1 -> SEG.B std::map>> connections; std::deque toCheck; auto closeEnough = - []( VECTOR2I aLeft, VECTOR2I aRight, unsigned aLimit ) + []( VECTOR2I aLeft, VECTOR2I aRight, int aLimit ) { - return ( aLeft - aRight ).SquaredEuclideanNorm() <= aLimit; + return ( aLeft - aRight ).SquaredEuclideanNorm() <= SEG::Square( aLimit ); }; auto findInsertionPoint = @@ -404,7 +443,7 @@ SHAPE_POLY_SET CONVERT_TOOL::makePolysFromChainedSegs( const std::dequeClearFlags( SKIP_STRUCT ); - - if( std::optional seg = getStartEndPoints( item, nullptr ) ) + if( std::optional seg = getStartEndPoints( item ) ) { toCheck.push_back( item ); connections[findInsertionPoint( seg->A )].emplace_back( std::make_pair( 0, item ) ); @@ -425,13 +462,14 @@ SHAPE_POLY_SET CONVERT_TOOL::makePolysFromChainedSegs( const std::deque insertedItems; + EDA_ITEM* candidate = toCheck.front(); toCheck.pop_front(); if( candidate->GetFlags() & SKIP_STRUCT ) continue; - int width = -1; SHAPE_LINE_CHAIN outline; auto insert = @@ -459,6 +497,8 @@ SHAPE_POLY_SET CONVERT_TOOL::makePolysFromChainedSegs( const std::deque( aItem ) ); } else if( aItem->IsType( { PCB_SHAPE_LOCATE_BEZIER_T } ) ) { @@ -489,18 +529,19 @@ SHAPE_POLY_SET CONVERT_TOOL::makePolysFromChainedSegs( const std::deque nextSeg = getStartEndPoints( aItem, &width ); - wxASSERT( nextSeg ); + insertedItems.push_back( static_cast( aItem ) ); + } + else if( std::optional nextSeg = getStartEndPoints( aItem ) ) + { VECTOR2I& point = ( aAnchor == nextSeg->A ) ? nextSeg->B : nextSeg->A; if( aDirection ) outline.Append( point ); else outline.Insert( 0, point ); + + insertedItems.push_back( static_cast( aItem ) ); } }; @@ -516,7 +557,7 @@ SHAPE_POLY_SET CONVERT_TOOL::makePolysFromChainedSegs( const std::deque anchors = getStartEndPoints( aItem, &width ); + std::optional anchors = getStartEndPoints( aItem ); wxASSERT( anchors ); VECTOR2I nextAnchor = ( aAnchor == anchors->A ) ? anchors->B : anchors->A; @@ -530,7 +571,7 @@ SHAPE_POLY_SET CONVERT_TOOL::makePolysFromChainedSegs( const std::deque anchors = getStartEndPoints( candidate, &width ); + std::optional anchors = getStartEndPoints( candidate ); wxASSERT( anchors ); // Start with the first object and walk "right" @@ -560,16 +601,30 @@ SHAPE_POLY_SET CONVERT_TOOL::makePolysFromChainedSegs( const std::dequeA, false ); - if( outline.PointCount() < 3 ) + if( outline.PointCount() < 3 + || !closeEnough( outline.GetPoint( 0 ), outline.GetPoint( -1 ), chainingEpsilon ) ) + { + for( EDA_ITEM* item : insertedItems ) + item->ClearFlags( SKIP_STRUCT ); + continue; + } outline.SetClosed( true ); outline.Simplify(); - if( width >= 0 ) - outline.SetWidth( width ); - poly.AddOutline( outline ); + + if( aStrategy == BOUNDING_HULL ) + { + for( BOARD_ITEM* item : insertedItems ) + { + item->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, bds.m_MaxError, + ERROR_INSIDE, false ); + } + } + + insertedItems.clear(); } return poly; @@ -591,14 +646,15 @@ SHAPE_POLY_SET CONVERT_TOOL::makePolysFromOpenGraphics( const std::deque( item->Clone() ); + PCB_SHAPE* shape = static_cast( item ); - if( temp->IsClosed() ) + if( shape->IsClosed() ) continue; - temp->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, bds.m_MaxError, ERROR_INSIDE, - false ); - item->SetFlags( SKIP_STRUCT ); + shape->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, bds.m_MaxError, ERROR_INSIDE, + false ); + shape->SetFlags( SKIP_STRUCT ); + break; } @@ -612,7 +668,7 @@ SHAPE_POLY_SET CONVERT_TOOL::makePolysFromOpenGraphics( const std::deque& aItems, - bool aIgnoreLineWidths ) + CONVERT_STRATEGY aStrategy ) { BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings(); SHAPE_POLY_SET poly; @@ -627,15 +683,18 @@ SHAPE_POLY_SET CONVERT_TOOL::makePolysFromClosedGraphics( const std::deque( item->Clone() ); + PCB_SHAPE* shape = static_cast( item ); + FILL_T wasFilled = shape->GetFillMode(); - if( !temp->IsClosed() ) + if( !shape->IsClosed() ) continue; - temp->SetFilled( true ); - temp->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, bds.m_MaxError, ERROR_INSIDE, - aIgnoreLineWidths ); - item->SetFlags( SKIP_STRUCT ); + shape->SetFilled( true ); + shape->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, bds.m_MaxError, ERROR_INSIDE, + aStrategy == COPY_LINEWIDTH || aStrategy == CENTERLINE ); + shape->SetFillMode( wasFilled ); + shape->SetFlags( SKIP_STRUCT ); + break; } @@ -917,7 +976,7 @@ int CONVERT_TOOL::SegmentToArc( const TOOL_EVENT& aEvent ) // Offset the midpoint along the normal a little bit so that it's more obviously an arc const double offsetRatio = 0.1; - if( std::optional seg = getStartEndPoints( source, nullptr ) ) + if( std::optional seg = getStartEndPoints( source ) ) { start = seg->A; end = seg->B; @@ -981,7 +1040,7 @@ int CONVERT_TOOL::SegmentToArc( const TOOL_EVENT& aEvent ) } -std::optional CONVERT_TOOL::getStartEndPoints( EDA_ITEM* aItem, int* aWidth ) +std::optional CONVERT_TOOL::getStartEndPoints( EDA_ITEM* aItem ) { switch( aItem->Type() ) { @@ -999,9 +1058,6 @@ std::optional CONVERT_TOOL::getStartEndPoints( EDA_ITEM* aItem, int* aWidth if( shape->GetStart() == shape->GetEnd() ) return std::nullopt; - if( aWidth ) - *aWidth = shape->GetWidth(); - return std::make_optional( VECTOR2I( shape->GetStart() ), VECTOR2I( shape->GetEnd() ) ); @@ -1013,20 +1069,12 @@ std::optional CONVERT_TOOL::getStartEndPoints( EDA_ITEM* aItem, int* aWidth case PCB_TRACE_T: { PCB_TRACK* line = static_cast( aItem ); - - if( aWidth ) - *aWidth = line->GetWidth(); - return std::make_optional( VECTOR2I( line->GetStart() ), VECTOR2I( line->GetEnd() ) ); } case PCB_ARC_T: { PCB_ARC* arc = static_cast( aItem ); - - if( aWidth ) - *aWidth = arc->GetWidth(); - return std::make_optional( VECTOR2I( arc->GetStart() ), VECTOR2I( arc->GetEnd() ) ); } diff --git a/pcbnew/tools/convert_tool.h b/pcbnew/tools/convert_tool.h index 98049f77a2..abe532828b 100644 --- a/pcbnew/tools/convert_tool.h +++ b/pcbnew/tools/convert_tool.h @@ -1,7 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * - * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors. * @author Jon Evans * * This program is free software; you can redistribute it and/or @@ -27,6 +27,7 @@ #include #include +#include class CONDITIONAL_MENU; class PCB_SELECTION_TOOL; @@ -70,7 +71,7 @@ private: * @param aItem is an item that has a start and end point. * @return a segment from start to end, or std::nullopt if invalid. */ - static std::optional getStartEndPoints( EDA_ITEM* aItem, int* aWidth ); + static std::optional getStartEndPoints( EDA_ITEM* aItem ); /** * Try to make polygons from chained segments in the selected items. @@ -82,7 +83,8 @@ private: * @param aItems is a list of items to process. * @return a #SHAPE_POLY_SET containing any polygons that were created. */ - SHAPE_POLY_SET makePolysFromChainedSegs( const std::deque& aItems ); + SHAPE_POLY_SET makePolysFromChainedSegs( const std::deque& aItems, + CONVERT_STRATEGY aStrategy ); /** * Make polygons from graphic shapes and zones. @@ -92,7 +94,7 @@ private: */ SHAPE_POLY_SET makePolysFromOpenGraphics( const std::deque& aItems ); SHAPE_POLY_SET makePolysFromClosedGraphics( const std::deque& aItems, - bool aIgnoreLineWidths ); + CONVERT_STRATEGY aStrategy ); PCB_SELECTION_TOOL* m_selectionTool; CONDITIONAL_MENU* m_menu;