You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1458 lines
49 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  5. * @author Jon Evans <jon@craftyjon.com>
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <bitmaps.h>
  25. #include <dialog_shim.h>
  26. #include <wx/statline.h>
  27. #include <wx/checkbox.h>
  28. #include <wx/button.h>
  29. #include <wx/radiobut.h>
  30. #include <widgets/unit_binder.h>
  31. #include <board.h>
  32. #include <board_commit.h>
  33. #include <board_design_settings.h>
  34. #include <collectors.h>
  35. #include <confirm.h>
  36. #include <convert_basic_shapes_to_polygon.h>
  37. #include <dialogs/dialog_outset_items.h>
  38. #include <footprint.h>
  39. #include <footprint_edit_frame.h>
  40. #include <geometry/shape_compound.h>
  41. #include <pcb_edit_frame.h>
  42. #include <pcb_shape.h>
  43. #include <pcb_track.h>
  44. #include <pad.h>
  45. #include <tool/tool_manager.h>
  46. #include <tools/edit_tool.h>
  47. #include <tools/pcb_actions.h>
  48. #include <tools/pcb_selection_tool.h>
  49. #include <tools/pcb_tool_utils.h>
  50. #include <trigo.h>
  51. #include <macros.h>
  52. #include <zone.h>
  53. #include "convert_tool.h"
  54. class CONVERT_SETTINGS_DIALOG : public DIALOG_SHIM
  55. {
  56. public:
  57. CONVERT_SETTINGS_DIALOG( EDA_DRAW_FRAME* aParent, CONVERT_SETTINGS* aSettings,
  58. bool aShowCopyLineWidthOption, bool aShowCenterlineOption,
  59. bool aShowBoundingHullOption ) :
  60. DIALOG_SHIM( aParent, wxID_ANY, _( "Conversion Settings" ), wxDefaultPosition,
  61. wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
  62. m_settings( aSettings )
  63. {
  64. // Allow each distinct version of the dialog to have a different size
  65. m_hash_key = TO_UTF8( wxString::Format( wxS( "%s%c%c%c" ),
  66. GetTitle(),
  67. aShowCopyLineWidthOption,
  68. aShowCenterlineOption,
  69. aShowBoundingHullOption ) );
  70. wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
  71. wxBoxSizer* topSizer = new wxBoxSizer( wxVERTICAL );
  72. SetSizer( mainSizer );
  73. m_rbMimicLineWidth = new wxRadioButton( this, wxID_ANY, _( "Copy line width of first object" ) );
  74. if( aShowCopyLineWidthOption )
  75. topSizer->Add( m_rbMimicLineWidth, 0, wxLEFT|wxRIGHT, 5 );
  76. else
  77. m_rbMimicLineWidth->Hide();
  78. m_rbCenterline = new wxRadioButton( this, wxID_ANY, _( "Use centerlines" ) );
  79. if( aShowCenterlineOption )
  80. {
  81. topSizer->AddSpacer( 6 );
  82. topSizer->Add( m_rbCenterline, 0, wxLEFT|wxRIGHT, 5 );
  83. }
  84. else
  85. {
  86. m_rbCenterline->Hide();
  87. }
  88. m_rbBoundingHull = new wxRadioButton( this, wxID_ANY, _( "Create bounding hull" ) );
  89. m_gapLabel = new wxStaticText( this, wxID_ANY, _( "Gap:" ) );
  90. m_gapCtrl = new wxTextCtrl( this, wxID_ANY );
  91. m_gapUnits = new wxStaticText( this, wxID_ANY, _( "mm" ) );
  92. m_gap = new UNIT_BINDER( aParent, m_gapLabel, m_gapCtrl, m_gapUnits );
  93. m_widthLabel = new wxStaticText( this, wxID_ANY, _( "Line width:" ) );
  94. m_widthCtrl = new wxTextCtrl( this, wxID_ANY );
  95. m_widthUnits = new wxStaticText( this, wxID_ANY, _( "mm" ) );
  96. m_width = new UNIT_BINDER( aParent, m_widthLabel, m_widthCtrl, m_widthUnits );
  97. if( aShowBoundingHullOption )
  98. {
  99. topSizer->AddSpacer( 6 );
  100. topSizer->Add( m_rbBoundingHull, 0, wxLEFT|wxRIGHT, 5 );
  101. wxBoxSizer* hullParamsSizer = new wxBoxSizer( wxHORIZONTAL );
  102. hullParamsSizer->Add( m_gapLabel, 0, wxALIGN_CENTRE_VERTICAL, 5 );
  103. hullParamsSizer->Add( m_gapCtrl, 1, wxALIGN_CENTRE_VERTICAL|wxLEFT|wxRIGHT, 3 );
  104. hullParamsSizer->Add( m_gapUnits, 0, wxALIGN_CENTRE_VERTICAL, 5 );
  105. hullParamsSizer->AddSpacer( 18 );
  106. hullParamsSizer->Add( m_widthLabel, 0, wxALIGN_CENTRE_VERTICAL, 5 );
  107. hullParamsSizer->Add( m_widthCtrl, 1, wxALIGN_CENTRE_VERTICAL|wxLEFT|wxRIGHT, 3 );
  108. hullParamsSizer->Add( m_widthUnits, 0, wxALIGN_CENTRE_VERTICAL, 5 );
  109. topSizer->AddSpacer( 2 );
  110. topSizer->Add( hullParamsSizer, 0, wxLEFT, 26 );
  111. topSizer->AddSpacer( 15 );
  112. }
  113. else
  114. {
  115. m_rbBoundingHull->Hide();
  116. m_gap->Show( false, true );
  117. m_width->Show( false, true );
  118. }
  119. m_cbDeleteOriginals = new wxCheckBox( this, wxID_ANY, _( "Delete source objects after conversion" ) );
  120. topSizer->Add( m_cbDeleteOriginals, 0, wxALL, 5 );
  121. mainSizer->Add( topSizer, 1, wxALL|wxEXPAND, 10 );
  122. wxBoxSizer* buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
  123. buttonsSizer->AddStretchSpacer();
  124. wxStdDialogButtonSizer* sdbSizer = new wxStdDialogButtonSizer();
  125. wxButton* sdbSizerOK = new wxButton( this, wxID_OK );
  126. sdbSizer->AddButton( sdbSizerOK );
  127. wxButton* sdbSizerCancel = new wxButton( this, wxID_CANCEL );
  128. sdbSizer->AddButton( sdbSizerCancel );
  129. sdbSizer->Realize();
  130. buttonsSizer->Add( sdbSizer, 1, 0, 5 );
  131. mainSizer->Add( buttonsSizer, 0, wxALL|wxEXPAND, 5 );
  132. SetupStandardButtons();
  133. m_rbMimicLineWidth->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
  134. wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ),
  135. nullptr, this );
  136. m_rbCenterline->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
  137. wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ),
  138. nullptr, this );
  139. m_rbBoundingHull->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
  140. wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ),
  141. nullptr, this );
  142. finishDialogSettings();
  143. }
  144. ~CONVERT_SETTINGS_DIALOG()
  145. {
  146. delete m_gap;
  147. delete m_width;
  148. };
  149. protected:
  150. bool TransferDataToWindow() override
  151. {
  152. switch( m_settings->m_Strategy )
  153. {
  154. case COPY_LINEWIDTH: m_rbMimicLineWidth->SetValue( true ); break;
  155. case CENTERLINE: m_rbCenterline->SetValue( true ); break;
  156. case BOUNDING_HULL: m_rbBoundingHull->SetValue( true ); break;
  157. }
  158. m_gap->Enable( m_rbBoundingHull->GetValue() );
  159. m_width->Enable( m_rbBoundingHull->GetValue() );
  160. m_gap->SetValue( m_settings->m_Gap );
  161. m_width->SetValue( m_settings->m_LineWidth );
  162. m_cbDeleteOriginals->SetValue( m_settings->m_DeleteOriginals );
  163. return true;
  164. }
  165. bool TransferDataFromWindow() override
  166. {
  167. if( m_rbBoundingHull->GetValue() )
  168. m_settings->m_Strategy = BOUNDING_HULL;
  169. else if( m_rbCenterline->GetValue() )
  170. m_settings->m_Strategy = CENTERLINE;
  171. else
  172. m_settings->m_Strategy = COPY_LINEWIDTH;
  173. m_settings->m_Gap = m_gap->GetIntValue();
  174. m_settings->m_LineWidth = m_width->GetIntValue();
  175. m_settings->m_DeleteOriginals = m_cbDeleteOriginals->GetValue();
  176. return true;
  177. }
  178. void onRadioButton( wxCommandEvent& aEvent )
  179. {
  180. m_gap->Enable( m_rbBoundingHull->GetValue() );
  181. m_width->Enable( m_rbBoundingHull->GetValue() );
  182. }
  183. private:
  184. CONVERT_SETTINGS* m_settings;
  185. wxRadioButton* m_rbMimicLineWidth;
  186. wxRadioButton* m_rbCenterline;
  187. wxRadioButton* m_rbBoundingHull;
  188. wxStaticText* m_gapLabel;
  189. wxTextCtrl* m_gapCtrl;
  190. wxStaticText* m_gapUnits;
  191. UNIT_BINDER* m_gap;
  192. wxStaticText* m_widthLabel;
  193. wxTextCtrl* m_widthCtrl;
  194. wxStaticText* m_widthUnits;
  195. UNIT_BINDER* m_width;
  196. wxCheckBox* m_cbDeleteOriginals;
  197. };
  198. CONVERT_TOOL::CONVERT_TOOL() :
  199. PCB_TOOL_BASE( "pcbnew.Convert" ),
  200. m_selectionTool( nullptr ),
  201. m_menu( nullptr ),
  202. m_frame( nullptr )
  203. {
  204. initUserSettings();
  205. }
  206. CONVERT_TOOL::~CONVERT_TOOL()
  207. {
  208. delete m_menu;
  209. }
  210. using S_C = SELECTION_CONDITIONS;
  211. using P_S_C = PCB_SELECTION_CONDITIONS;
  212. bool CONVERT_TOOL::Init()
  213. {
  214. m_selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  215. m_frame = getEditFrame<PCB_BASE_FRAME>();
  216. // Create a context menu and make it available through selection tool
  217. m_menu = new CONDITIONAL_MENU( this );
  218. m_menu->SetIcon( BITMAPS::convert );
  219. m_menu->SetTitle( _( "Create from Selection" ) );
  220. static const std::vector<KICAD_T> padTypes = { PCB_PAD_T };
  221. static const std::vector<KICAD_T> toArcTypes = { PCB_ARC_T,
  222. PCB_TRACE_T,
  223. PCB_SHAPE_LOCATE_SEGMENT_T };
  224. static const std::vector<KICAD_T> shapeTypes = { PCB_SHAPE_LOCATE_SEGMENT_T,
  225. PCB_SHAPE_LOCATE_RECT_T,
  226. PCB_SHAPE_LOCATE_CIRCLE_T,
  227. PCB_SHAPE_LOCATE_ARC_T,
  228. PCB_SHAPE_LOCATE_BEZIER_T,
  229. PCB_FIELD_T,
  230. PCB_TEXT_T };
  231. static const std::vector<KICAD_T> trackTypes = { PCB_TRACE_T,
  232. PCB_ARC_T,
  233. PCB_VIA_T };
  234. static const std::vector<KICAD_T> toTrackTypes = { PCB_SHAPE_LOCATE_SEGMENT_T,
  235. PCB_SHAPE_LOCATE_ARC_T };
  236. static const std::vector<KICAD_T> polyTypes = { PCB_ZONE_T,
  237. PCB_SHAPE_LOCATE_POLY_T,
  238. PCB_SHAPE_LOCATE_RECT_T };
  239. static const std::vector<KICAD_T> outsetTypes = { PCB_PAD_T, PCB_SHAPE_T };
  240. auto shapes = S_C::OnlyTypes( shapeTypes ) && P_S_C::SameLayer();
  241. auto graphicToTrack = S_C::OnlyTypes( toTrackTypes );
  242. auto anyTracks = S_C::MoreThan( 0 ) && S_C::OnlyTypes( trackTypes ) && P_S_C::SameLayer();
  243. auto anyPolys = S_C::OnlyTypes( polyTypes );
  244. auto anyPads = S_C::OnlyTypes( padTypes );
  245. auto canCreateArcs = S_C::Count( 1 ) && S_C::OnlyTypes( toArcTypes );
  246. auto canCreateArray = S_C::MoreThan( 0 );
  247. auto canCreatePoly = shapes || anyPolys || anyTracks;
  248. auto canCreateOutset = S_C::OnlyTypes( outsetTypes );
  249. if( m_frame->IsType( FRAME_FOOTPRINT_EDITOR ) )
  250. canCreatePoly = shapes || anyPolys || anyTracks || anyPads;
  251. auto canCreateLines = anyPolys;
  252. auto canCreateTracks = anyPolys || graphicToTrack;
  253. auto canCreate = canCreatePoly
  254. || canCreateLines
  255. || canCreateTracks
  256. || canCreateArcs
  257. || canCreateArray
  258. || canCreateOutset;
  259. m_menu->AddItem( PCB_ACTIONS::convertToPoly, canCreatePoly );
  260. if( m_frame->IsType( FRAME_PCB_EDITOR ) )
  261. m_menu->AddItem( PCB_ACTIONS::convertToZone, canCreatePoly );
  262. m_menu->AddItem( PCB_ACTIONS::convertToKeepout, canCreatePoly );
  263. m_menu->AddItem( PCB_ACTIONS::convertToLines, canCreateLines );
  264. m_menu->AddItem( PCB_ACTIONS::outsetItems, canCreateOutset );
  265. m_menu->AddSeparator();
  266. // Currently the code exists, but tracks are not really existing in footprints
  267. // only segments on copper layers
  268. if( m_frame->IsType( FRAME_PCB_EDITOR ) )
  269. m_menu->AddItem( PCB_ACTIONS::convertToTracks, canCreateTracks );
  270. m_menu->AddItem( PCB_ACTIONS::convertToArc, canCreateArcs );
  271. m_menu->AddSeparator();
  272. m_menu->AddItem( PCB_ACTIONS::createArray, canCreateArray );
  273. CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
  274. selToolMenu.AddMenu( m_menu, canCreate, 100 );
  275. return true;
  276. }
  277. void CONVERT_TOOL::initUserSettings()
  278. {
  279. m_userSettings.m_Strategy = CENTERLINE;
  280. m_userSettings.m_Gap = 0;
  281. m_userSettings.m_LineWidth = 0;
  282. m_userSettings.m_DeleteOriginals = true;
  283. }
  284. int CONVERT_TOOL::CreatePolys( const TOOL_EVENT& aEvent )
  285. {
  286. BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
  287. std::vector<SHAPE_POLY_SET> polys;
  288. PCB_LAYER_ID destLayer = m_frame->GetActiveLayer();
  289. FOOTPRINT* parentFootprint = nullptr;
  290. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  291. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  292. {
  293. } );
  294. if( selection.Empty() )
  295. return 0;
  296. auto getPolys =
  297. [&]( CONVERT_SETTINGS cfg )
  298. {
  299. polys.clear();
  300. for( EDA_ITEM* item : selection )
  301. item->ClearTempFlags();
  302. SHAPE_POLY_SET polySet;
  303. polySet.Append( makePolysFromClosedGraphics( selection.GetItems(), cfg.m_Strategy ) );
  304. if( cfg.m_Strategy == BOUNDING_HULL )
  305. {
  306. polySet.Append( makePolysFromOpenGraphics( selection.GetItems(), 0 ) );
  307. polySet.ClearArcs();
  308. polySet.Simplify();
  309. // Now inflate the bounding hull by cfg.m_Gap
  310. polySet.Inflate( cfg.m_Gap, CORNER_STRATEGY::ROUND_ALL_CORNERS, bds.m_MaxError,
  311. ERROR_OUTSIDE );
  312. }
  313. else
  314. {
  315. polySet.Append( makePolysFromChainedSegs( selection.GetItems(), cfg.m_Strategy ) );
  316. }
  317. if( polySet.IsEmpty() )
  318. return false;
  319. for( int ii = 0; ii < polySet.OutlineCount(); ++ii )
  320. {
  321. polys.emplace_back( SHAPE_POLY_SET( polySet.COutline( ii ) ) );
  322. for( int jj = 0; jj < polySet.HoleCount( ii ); ++jj )
  323. polys.back().AddHole( polySet.Hole( ii, jj ) );
  324. }
  325. return true;
  326. };
  327. // Pre-flight getPolys() to see if there's anything to convert.
  328. CONVERT_SETTINGS preflightSettings = m_userSettings;
  329. preflightSettings.m_Strategy = BOUNDING_HULL;
  330. if( !getPolys( preflightSettings ) )
  331. return 0;
  332. if( selection.Front() && selection.Front()->IsBOARD_ITEM() )
  333. parentFootprint = static_cast<BOARD_ITEM*>( selection.Front() )->GetParentFootprint();
  334. PCB_LAYER_ID layer = m_frame->GetActiveLayer();
  335. BOARD_COMMIT commit( m_frame );
  336. if( aEvent.IsAction( &PCB_ACTIONS::convertToPoly ) )
  337. {
  338. bool showCopyLineWidth = true;
  339. // No copy-line-width option for pads
  340. if( selection.Front()->Type() == PCB_PAD_T )
  341. {
  342. if( m_userSettings.m_Strategy == COPY_LINEWIDTH )
  343. m_userSettings.m_Strategy = CENTERLINE;
  344. showCopyLineWidth = false;
  345. }
  346. CONVERT_SETTINGS previousSettings = m_userSettings;
  347. CONVERT_SETTINGS_DIALOG dlg( m_frame, &m_userSettings, showCopyLineWidth, true, true );
  348. if( dlg.ShowModal() != wxID_OK )
  349. return 0;
  350. CONVERT_SETTINGS resolvedSettings = m_userSettings;
  351. if( resolvedSettings.m_Strategy != CENTERLINE )
  352. {
  353. if( resolvedSettings.m_LineWidth == 0 )
  354. resolvedSettings.m_LineWidth = bds.m_LineThickness[ bds.GetLayerClass( layer ) ];
  355. }
  356. if( resolvedSettings.m_Strategy == BOUNDING_HULL )
  357. {
  358. if( resolvedSettings.m_Gap > 0 )
  359. resolvedSettings.m_Gap += KiROUND( (double) resolvedSettings.m_LineWidth / 2 );
  360. }
  361. if( !getPolys( resolvedSettings ) )
  362. {
  363. wxString msg;
  364. if( resolvedSettings.m_Strategy == BOUNDING_HULL )
  365. msg = _( "Resulting polygon would be empty" );
  366. else
  367. msg = _( "Objects must form a closed shape" );
  368. DisplayErrorMessage( m_frame, _( "Could not convert selection" ), msg );
  369. m_userSettings = previousSettings;
  370. return 0;
  371. }
  372. for( const SHAPE_POLY_SET& poly : polys )
  373. {
  374. PCB_SHAPE* graphic = new PCB_SHAPE( parentFootprint );
  375. if( resolvedSettings.m_Strategy == COPY_LINEWIDTH )
  376. {
  377. BOARD_ITEM* topLeftItem = nullptr;
  378. VECTOR2I pos;
  379. for( EDA_ITEM* item : selection )
  380. {
  381. if( !item->IsBOARD_ITEM() )
  382. continue;
  383. BOARD_ITEM* candidate = static_cast<BOARD_ITEM*>( item );
  384. if( candidate->HasLineStroke() )
  385. {
  386. pos = candidate->GetPosition();
  387. if( !topLeftItem
  388. || ( pos.x < topLeftItem->GetPosition().x )
  389. || ( topLeftItem->GetPosition().x == pos.x
  390. && pos.y < topLeftItem->GetPosition().y ) )
  391. {
  392. topLeftItem = candidate;
  393. resolvedSettings.m_LineWidth = topLeftItem->GetStroke().GetWidth();
  394. }
  395. }
  396. }
  397. }
  398. graphic->SetShape( SHAPE_T::POLY );
  399. graphic->SetStroke( STROKE_PARAMS( resolvedSettings.m_LineWidth, LINE_STYLE::SOLID,
  400. COLOR4D::UNSPECIFIED ) );
  401. graphic->SetFilled( resolvedSettings.m_Strategy == CENTERLINE );
  402. graphic->SetLayer( destLayer );
  403. graphic->SetPolyShape( poly );
  404. commit.Add( graphic );
  405. }
  406. }
  407. else
  408. {
  409. // Creating zone or keepout
  410. PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  411. BOARD_ITEM_CONTAINER* parent = frame->GetModel();
  412. ZONE_SETTINGS zoneInfo = bds.GetDefaultZoneSettings();
  413. bool nonCopper = IsNonCopperLayer( destLayer );
  414. zoneInfo.m_Layers.reset().set( destLayer );
  415. zoneInfo.m_Name.Empty();
  416. int ret;
  417. // No copy-line-width option for zones/keepouts
  418. if( m_userSettings.m_Strategy == COPY_LINEWIDTH )
  419. m_userSettings.m_Strategy = CENTERLINE;
  420. if( aEvent.IsAction( &PCB_ACTIONS::convertToKeepout ) )
  421. {
  422. zoneInfo.SetIsRuleArea( true );
  423. ret = InvokeRuleAreaEditor( frame, &zoneInfo, board(), &m_userSettings );
  424. }
  425. else if( nonCopper )
  426. {
  427. zoneInfo.SetIsRuleArea( false );
  428. ret = InvokeNonCopperZonesEditor( frame, &zoneInfo, &m_userSettings );
  429. }
  430. else
  431. {
  432. zoneInfo.SetIsRuleArea( false );
  433. ret = InvokeCopperZonesEditor( frame, &zoneInfo, &m_userSettings );
  434. }
  435. if( ret == wxID_CANCEL )
  436. return 0;
  437. if( !getPolys( m_userSettings ) )
  438. return 0;
  439. for( const SHAPE_POLY_SET& poly : polys )
  440. {
  441. ZONE* zone = new ZONE( parent );
  442. *zone->Outline() = poly;
  443. zone->HatchBorder();
  444. zoneInfo.ExportSetting( *zone );
  445. commit.Add( zone );
  446. }
  447. }
  448. if( m_userSettings.m_DeleteOriginals )
  449. {
  450. PCB_SELECTION selectionCopy = selection;
  451. m_selectionTool->ClearSelection();
  452. for( EDA_ITEM* item : selectionCopy )
  453. {
  454. if( item->GetFlags() & SKIP_STRUCT )
  455. commit.Remove( item );
  456. }
  457. }
  458. if( aEvent.IsAction( &PCB_ACTIONS::convertToPoly ) )
  459. {
  460. if( m_userSettings.m_DeleteOriginals )
  461. commit.Push( _( "Convert to Polygon" ) );
  462. else
  463. commit.Push( _( "Create Polygon" ) );
  464. }
  465. else
  466. {
  467. if( m_userSettings.m_DeleteOriginals )
  468. commit.Push( _( "Convert to Zone" ) );
  469. else
  470. commit.Push( _( "Create Zone" ) );
  471. }
  472. return 0;
  473. }
  474. SHAPE_POLY_SET CONVERT_TOOL::makePolysFromChainedSegs( const std::deque<EDA_ITEM*>& aItems,
  475. CONVERT_STRATEGY aStrategy )
  476. {
  477. // TODO: This code has a somewhat-similar purpose to ConvertOutlineToPolygon but is slightly
  478. // different, so this remains a separate algorithm. It might be nice to analyze the dfiferences
  479. // in requirements and refactor this.
  480. // Using a large epsilon here to allow for sloppy drawing can cause the algorithm to miss very
  481. // short segments in a converted bezier. So use an epsilon only large enough to cover for
  482. // rouding errors in the conversion.
  483. int chainingEpsilon = 100; // max dist from one endPt to next startPt in IU
  484. BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
  485. SHAPE_POLY_SET poly;
  486. // Stores pairs of (anchor, item) where anchor == 0 -> SEG.A, anchor == 1 -> SEG.B
  487. std::map<VECTOR2I, std::vector<std::pair<int, EDA_ITEM*>>> connections;
  488. std::deque<EDA_ITEM*> toCheck;
  489. auto closeEnough =
  490. []( const VECTOR2I& aLeft, const VECTOR2I& aRight, int aLimit )
  491. {
  492. return ( aLeft - aRight ).SquaredEuclideanNorm() <= SEG::Square( aLimit );
  493. };
  494. auto findInsertionPoint =
  495. [&]( const VECTOR2I& aPoint ) -> VECTOR2I
  496. {
  497. if( connections.count( aPoint ) )
  498. return aPoint;
  499. for( const auto& candidatePair : connections )
  500. {
  501. if( closeEnough( aPoint, candidatePair.first, chainingEpsilon ) )
  502. return candidatePair.first;
  503. }
  504. return aPoint;
  505. };
  506. for( EDA_ITEM* item : aItems )
  507. {
  508. if( std::optional<SEG> seg = getStartEndPoints( item ) )
  509. {
  510. toCheck.push_back( item );
  511. connections[findInsertionPoint( seg->A )].emplace_back( std::make_pair( 0, item ) );
  512. connections[findInsertionPoint( seg->B )].emplace_back( std::make_pair( 1, item ) );
  513. }
  514. }
  515. while( !toCheck.empty() )
  516. {
  517. std::vector<BOARD_ITEM*> insertedItems;
  518. EDA_ITEM* candidate = toCheck.front();
  519. toCheck.pop_front();
  520. if( candidate->GetFlags() & SKIP_STRUCT )
  521. continue;
  522. SHAPE_LINE_CHAIN outline;
  523. auto insert =
  524. [&]( EDA_ITEM* aItem, const VECTOR2I& aAnchor, bool aDirection )
  525. {
  526. if( aItem->Type() == PCB_ARC_T
  527. || ( aItem->Type() == PCB_SHAPE_T
  528. && static_cast<PCB_SHAPE*>( aItem )->GetShape() == SHAPE_T::ARC ) )
  529. {
  530. SHAPE_ARC arc;
  531. if( aItem->Type() == PCB_ARC_T )
  532. {
  533. PCB_ARC* pcb_arc = static_cast<PCB_ARC*>( aItem );
  534. arc = *static_cast<SHAPE_ARC*>( pcb_arc->GetEffectiveShape().get() );
  535. }
  536. else
  537. {
  538. PCB_SHAPE* pcb_shape = static_cast<PCB_SHAPE*>( aItem );
  539. arc = SHAPE_ARC( pcb_shape->GetStart(), pcb_shape->GetArcMid(),
  540. pcb_shape->GetEnd(), pcb_shape->GetWidth() );
  541. }
  542. if( aDirection )
  543. outline.Append( aAnchor == arc.GetP0() ? arc : arc.Reversed() );
  544. else
  545. outline.Insert( 0, aAnchor == arc.GetP0() ? arc : arc.Reversed() );
  546. insertedItems.push_back( static_cast<BOARD_ITEM*>( aItem ) );
  547. }
  548. else if( aItem->IsType( { PCB_SHAPE_LOCATE_BEZIER_T } ) )
  549. {
  550. PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
  551. if( aAnchor == graphic->GetStart() )
  552. {
  553. for( auto it = graphic->GetBezierPoints().begin();
  554. it != graphic->GetBezierPoints().end();
  555. ++it )
  556. {
  557. if( aDirection )
  558. outline.Append( *it );
  559. else
  560. outline.Insert( 0, *it );
  561. }
  562. }
  563. else
  564. {
  565. for( auto it = graphic->GetBezierPoints().rbegin();
  566. it != graphic->GetBezierPoints().rend();
  567. ++it )
  568. {
  569. if( aDirection )
  570. outline.Append( *it );
  571. else
  572. outline.Insert( 0, *it );
  573. }
  574. }
  575. insertedItems.push_back( static_cast<BOARD_ITEM*>( aItem ) );
  576. }
  577. else if( std::optional<SEG> nextSeg = getStartEndPoints( aItem ) )
  578. {
  579. VECTOR2I& point = ( aAnchor == nextSeg->A ) ? nextSeg->B : nextSeg->A;
  580. if( aDirection )
  581. outline.Append( point );
  582. else
  583. outline.Insert( 0, point );
  584. insertedItems.push_back( static_cast<BOARD_ITEM*>( aItem ) );
  585. }
  586. };
  587. // aDirection == true for walking "right" and appending to the end of points
  588. // false for walking "left" and prepending to the beginning
  589. std::function<void( EDA_ITEM*, const VECTOR2I&, bool )> process =
  590. [&]( EDA_ITEM* aItem, const VECTOR2I& aAnchor, bool aDirection )
  591. {
  592. if( aItem->GetFlags() & SKIP_STRUCT )
  593. return;
  594. aItem->SetFlags( SKIP_STRUCT );
  595. insert( aItem, aAnchor, aDirection );
  596. std::optional<SEG> anchors = getStartEndPoints( aItem );
  597. wxASSERT( anchors );
  598. VECTOR2I nextAnchor = ( aAnchor == anchors->A ) ? anchors->B : anchors->A;
  599. for( std::pair<int, EDA_ITEM*> pair : connections[nextAnchor] )
  600. {
  601. if( pair.second == aItem )
  602. continue;
  603. process( pair.second, nextAnchor, aDirection );
  604. }
  605. };
  606. std::optional<SEG> anchors = getStartEndPoints( candidate );
  607. wxASSERT( anchors );
  608. // Start with the first object and walk "right"
  609. // Note if the first object is an arc, we don't need to insert its first point here, the
  610. // whole arc will be inserted at anchor B inside process()
  611. if( !( candidate->Type() == PCB_ARC_T
  612. || ( candidate->Type() == PCB_SHAPE_T
  613. && static_cast<PCB_SHAPE*>( candidate )->GetShape() == SHAPE_T::ARC ) ) )
  614. {
  615. insert( candidate, anchors->A, true );
  616. }
  617. process( candidate, anchors->B, true );
  618. // check for any candidates on the "left"
  619. EDA_ITEM* left = nullptr;
  620. for( std::pair<int, EDA_ITEM*> possibleLeft : connections[anchors->A] )
  621. {
  622. if( possibleLeft.second != candidate )
  623. {
  624. left = possibleLeft.second;
  625. break;
  626. }
  627. }
  628. if( left )
  629. process( left, anchors->A, false );
  630. if( outline.PointCount() < 3
  631. || !closeEnough( outline.GetPoint( 0 ), outline.GetPoint( -1 ), chainingEpsilon ) )
  632. {
  633. for( EDA_ITEM* item : insertedItems )
  634. item->ClearFlags( SKIP_STRUCT );
  635. continue;
  636. }
  637. outline.SetClosed( true );
  638. poly.AddOutline( outline );
  639. if( aStrategy == BOUNDING_HULL )
  640. {
  641. for( BOARD_ITEM* item : insertedItems )
  642. {
  643. item->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, bds.m_MaxError,
  644. ERROR_INSIDE, false );
  645. }
  646. }
  647. insertedItems.clear();
  648. }
  649. return poly;
  650. }
  651. SHAPE_POLY_SET CONVERT_TOOL::makePolysFromOpenGraphics( const std::deque<EDA_ITEM*>& aItems,
  652. int aGap )
  653. {
  654. BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
  655. SHAPE_POLY_SET poly;
  656. for( EDA_ITEM* item : aItems )
  657. {
  658. if( item->GetFlags() & SKIP_STRUCT )
  659. continue;
  660. switch( item->Type() )
  661. {
  662. case PCB_SHAPE_T:
  663. {
  664. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
  665. if( shape->IsClosed() )
  666. continue;
  667. shape->TransformShapeToPolygon( poly, UNDEFINED_LAYER, aGap, bds.m_MaxError,
  668. ERROR_INSIDE, false );
  669. shape->SetFlags( SKIP_STRUCT );
  670. break;
  671. }
  672. case PCB_TRACE_T:
  673. case PCB_ARC_T:
  674. case PCB_VIA_T:
  675. {
  676. PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
  677. track->TransformShapeToPolygon( poly, UNDEFINED_LAYER, aGap, bds.m_MaxError,
  678. ERROR_INSIDE, false );
  679. track->SetFlags( SKIP_STRUCT );
  680. break;
  681. }
  682. default:
  683. continue;
  684. }
  685. }
  686. return poly;
  687. }
  688. SHAPE_POLY_SET CONVERT_TOOL::makePolysFromClosedGraphics( const std::deque<EDA_ITEM*>& aItems,
  689. CONVERT_STRATEGY aStrategy )
  690. {
  691. BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
  692. SHAPE_POLY_SET poly;
  693. for( EDA_ITEM* item : aItems )
  694. {
  695. if( item->GetFlags() & SKIP_STRUCT )
  696. continue;
  697. switch( item->Type() )
  698. {
  699. case PCB_SHAPE_T:
  700. {
  701. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
  702. FILL_T wasFilled = shape->GetFillMode();
  703. if( !shape->IsClosed() )
  704. continue;
  705. if( aStrategy != BOUNDING_HULL )
  706. shape->SetFilled( true );
  707. shape->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, bds.m_MaxError, ERROR_INSIDE,
  708. aStrategy == COPY_LINEWIDTH
  709. || aStrategy == CENTERLINE );
  710. if( aStrategy != BOUNDING_HULL )
  711. shape->SetFillMode( wasFilled );
  712. shape->SetFlags( SKIP_STRUCT );
  713. break;
  714. }
  715. case PCB_ZONE_T:
  716. poly.Append( *static_cast<ZONE*>( item )->Outline() );
  717. item->SetFlags( SKIP_STRUCT );
  718. break;
  719. case PCB_FIELD_T:
  720. case PCB_TEXT_T:
  721. {
  722. PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
  723. text->TransformTextToPolySet( poly, 0, bds.m_MaxError, ERROR_INSIDE );
  724. text->SetFlags( SKIP_STRUCT );
  725. break;
  726. }
  727. case PCB_PAD_T:
  728. {
  729. PAD* pad = static_cast<PAD*>( item );
  730. pad->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, bds.m_MaxError, ERROR_INSIDE,
  731. false );
  732. pad->SetFlags( SKIP_STRUCT );
  733. break;
  734. }
  735. default:
  736. continue;
  737. }
  738. }
  739. return poly;
  740. }
  741. int CONVERT_TOOL::CreateLines( const TOOL_EVENT& aEvent )
  742. {
  743. auto& selection = m_selectionTool->RequestSelection(
  744. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  745. {
  746. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  747. {
  748. BOARD_ITEM* item = aCollector[i];
  749. switch( item->Type() )
  750. {
  751. case PCB_SHAPE_T:
  752. switch( static_cast<PCB_SHAPE*>( item )->GetShape() )
  753. {
  754. case SHAPE_T::SEGMENT:
  755. case SHAPE_T::ARC:
  756. case SHAPE_T::POLY:
  757. case SHAPE_T::RECTANGLE:
  758. break;
  759. default:
  760. aCollector.Remove( item );
  761. }
  762. break;
  763. case PCB_ZONE_T:
  764. break;
  765. default:
  766. aCollector.Remove( item );
  767. }
  768. }
  769. } );
  770. if( selection.Empty() )
  771. return 0;
  772. auto getPolySet =
  773. []( EDA_ITEM* aItem )
  774. {
  775. SHAPE_POLY_SET set;
  776. switch( aItem->Type() )
  777. {
  778. case PCB_ZONE_T:
  779. set = *static_cast<ZONE*>( aItem )->Outline();
  780. break;
  781. case PCB_SHAPE_T:
  782. {
  783. PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
  784. if( graphic->GetShape() == SHAPE_T::POLY )
  785. {
  786. set = graphic->GetPolyShape();
  787. }
  788. else if( graphic->GetShape() == SHAPE_T::RECTANGLE )
  789. {
  790. SHAPE_LINE_CHAIN outline;
  791. VECTOR2I start( graphic->GetStart() );
  792. VECTOR2I end( graphic->GetEnd() );
  793. outline.Append( start );
  794. outline.Append( VECTOR2I( end.x, start.y ) );
  795. outline.Append( end );
  796. outline.Append( VECTOR2I( start.x, end.y ) );
  797. outline.SetClosed( true );
  798. set.AddOutline( outline );
  799. }
  800. else
  801. {
  802. wxFAIL_MSG( wxT( "Unhandled graphic shape type in PolyToLines - getPolySet" ) );
  803. }
  804. break;
  805. }
  806. default:
  807. wxFAIL_MSG( wxT( "Unhandled type in PolyToLines - getPolySet" ) );
  808. break;
  809. }
  810. return set;
  811. };
  812. auto getSegList =
  813. []( SHAPE_POLY_SET& aPoly )
  814. {
  815. std::vector<SEG> segs;
  816. // Our input should be valid polys, so OK to assert here
  817. wxASSERT( aPoly.VertexCount() >= 2 );
  818. for( int i = 1; i < aPoly.VertexCount(); i++ )
  819. segs.emplace_back( SEG( aPoly.CVertex( i - 1 ), aPoly.CVertex( i ) ) );
  820. segs.emplace_back( SEG( aPoly.CVertex( aPoly.VertexCount() - 1 ),
  821. aPoly.CVertex( 0 ) ) );
  822. return segs;
  823. };
  824. BOARD_COMMIT commit( m_frame );
  825. PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  826. FOOTPRINT_EDIT_FRAME* fpEditor = dynamic_cast<FOOTPRINT_EDIT_FRAME*>( m_frame );
  827. FOOTPRINT* footprint = nullptr;
  828. PCB_LAYER_ID targetLayer = m_frame->GetActiveLayer();
  829. BOARD_ITEM_CONTAINER* parent = frame->GetModel();
  830. if( fpEditor )
  831. footprint = fpEditor->GetBoard()->GetFirstFootprint();
  832. auto handleGraphicSeg =
  833. [&]( EDA_ITEM* aItem )
  834. {
  835. if( aItem->Type() != PCB_SHAPE_T )
  836. return false;
  837. PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
  838. if( graphic->GetShape() == SHAPE_T::SEGMENT )
  839. {
  840. PCB_TRACK* track = new PCB_TRACK( parent );
  841. track->SetLayer( targetLayer );
  842. track->SetStart( graphic->GetStart() );
  843. track->SetEnd( graphic->GetEnd() );
  844. track->SetWidth( graphic->GetWidth() );
  845. commit.Add( track );
  846. return true;
  847. }
  848. else if( graphic->GetShape() == SHAPE_T::ARC )
  849. {
  850. PCB_ARC* arc = new PCB_ARC( parent );
  851. arc->SetLayer( targetLayer );
  852. arc->SetStart( graphic->GetStart() );
  853. arc->SetEnd( graphic->GetEnd() );
  854. arc->SetMid( graphic->GetArcMid() );
  855. arc->SetWidth( graphic->GetWidth() );
  856. commit.Add( arc );
  857. return true;
  858. }
  859. return false;
  860. };
  861. if( aEvent.IsAction( &PCB_ACTIONS::convertToTracks ) )
  862. {
  863. if( !IsCopperLayer( targetLayer ) )
  864. {
  865. targetLayer = frame->SelectOneLayer( F_Cu, LSET::AllNonCuMask() );
  866. if( targetLayer == UNDEFINED_LAYER ) // User canceled
  867. return true;
  868. }
  869. }
  870. else
  871. {
  872. CONVERT_SETTINGS_DIALOG dlg( m_frame, &m_userSettings, false, false, false );
  873. if( dlg.ShowModal() != wxID_OK )
  874. return true;
  875. }
  876. for( EDA_ITEM* item : selection )
  877. {
  878. if( handleGraphicSeg( item ) )
  879. continue;
  880. BOARD_ITEM& boardItem = static_cast<BOARD_ITEM&>( *item );
  881. SHAPE_POLY_SET polySet = getPolySet( item );
  882. std::vector<SEG> segs = getSegList( polySet );
  883. std::optional<int> itemWidth = GetBoardItemWidth( boardItem );
  884. if( aEvent.IsAction( &PCB_ACTIONS::convertToLines ) )
  885. {
  886. for( SEG& seg : segs )
  887. {
  888. PCB_SHAPE* graphic = new PCB_SHAPE( footprint, SHAPE_T::SEGMENT );
  889. graphic->SetLayer( targetLayer );
  890. graphic->SetStart( VECTOR2I( seg.A ) );
  891. graphic->SetEnd( VECTOR2I( seg.B ) );
  892. // The width can exist but be 0 for filled, unstroked shapes
  893. if( itemWidth && *itemWidth > 0 )
  894. graphic->SetWidth( *itemWidth );
  895. commit.Add( graphic );
  896. }
  897. }
  898. else
  899. {
  900. // I am really unsure converting a polygon to "tracks" (i.e. segments on
  901. // copper layers) make sense for footprints, but anyway this code exists
  902. if( fpEditor )
  903. {
  904. // Creating segments on copper layer
  905. for( SEG& seg : segs )
  906. {
  907. PCB_SHAPE* graphic = new PCB_SHAPE( footprint, SHAPE_T::SEGMENT );
  908. graphic->SetLayer( targetLayer );
  909. graphic->SetStart( VECTOR2I( seg.A ) );
  910. graphic->SetEnd( VECTOR2I( seg.B ) );
  911. if( itemWidth )
  912. graphic->SetWidth( *itemWidth );
  913. commit.Add( graphic );
  914. }
  915. }
  916. else
  917. {
  918. // Creating tracks
  919. for( SEG& seg : segs )
  920. {
  921. PCB_TRACK* track = new PCB_TRACK( parent );
  922. track->SetLayer( targetLayer );
  923. track->SetStart( VECTOR2I( seg.A ) );
  924. track->SetEnd( VECTOR2I( seg.B ) );
  925. commit.Add( track );
  926. }
  927. }
  928. }
  929. }
  930. if( m_userSettings.m_DeleteOriginals )
  931. {
  932. PCB_SELECTION selectionCopy = selection;
  933. m_selectionTool->ClearSelection();
  934. for( EDA_ITEM* item : selectionCopy )
  935. commit.Remove( item );
  936. }
  937. commit.Push( _( "Create Lines" ) );
  938. return 0;
  939. }
  940. int CONVERT_TOOL::SegmentToArc( const TOOL_EVENT& aEvent )
  941. {
  942. auto& selection = m_selectionTool->RequestSelection(
  943. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  944. {
  945. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  946. {
  947. BOARD_ITEM* item = aCollector[i];
  948. if( !( item->Type() == PCB_SHAPE_T ||
  949. item->Type() == PCB_TRACE_T ||
  950. item->Type() == PCB_ARC_T ) )
  951. {
  952. aCollector.Remove( item );
  953. }
  954. }
  955. } );
  956. if( selection.Empty() || !selection.Front()->IsBOARD_ITEM() )
  957. return -1;
  958. BOARD_ITEM* source = static_cast<BOARD_ITEM*>( selection.Front() );
  959. VECTOR2I start, end, mid;
  960. // Offset the midpoint along the normal a little bit so that it's more obviously an arc
  961. const double offsetRatio = 0.1;
  962. if( std::optional<SEG> seg = getStartEndPoints( source ) )
  963. {
  964. start = seg->A;
  965. end = seg->B;
  966. VECTOR2I normal = ( seg->B - seg->A ).Perpendicular().Resize( offsetRatio * seg->Length() );
  967. mid = seg->Center() + normal;
  968. }
  969. else
  970. {
  971. return -1;
  972. }
  973. PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  974. BOARD_ITEM_CONTAINER* parent = frame->GetModel();
  975. PCB_LAYER_ID layer = source->GetLayer();
  976. BOARD_COMMIT commit( m_frame );
  977. if( source->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( source )->GetShape() == SHAPE_T::SEGMENT )
  978. {
  979. PCB_SHAPE* line = static_cast<PCB_SHAPE*>( source );
  980. PCB_SHAPE* arc = new PCB_SHAPE( parent, SHAPE_T::ARC );
  981. VECTOR2I center = CalcArcCenter( start, mid, end );
  982. arc->SetFilled( false );
  983. arc->SetLayer( layer );
  984. arc->SetStroke( line->GetStroke() );
  985. arc->SetCenter( center );
  986. arc->SetStart( start );
  987. arc->SetEnd( end );
  988. commit.Add( arc );
  989. }
  990. else if( source->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( source )->GetShape() == SHAPE_T::ARC )
  991. {
  992. PCB_SHAPE* source_arc = static_cast<PCB_SHAPE*>( source );
  993. PCB_ARC* arc = new PCB_ARC( parent );
  994. arc->SetLayer( layer );
  995. arc->SetWidth( source_arc->GetWidth() );
  996. arc->SetStart( start );
  997. arc->SetMid( source_arc->GetArcMid() );
  998. arc->SetEnd( end );
  999. commit.Add( arc );
  1000. }
  1001. else if( source->Type() == PCB_TRACE_T )
  1002. {
  1003. PCB_TRACK* line = static_cast<PCB_TRACK*>( source );
  1004. PCB_ARC* arc = new PCB_ARC( parent );
  1005. arc->SetLayer( layer );
  1006. arc->SetWidth( line->GetWidth() );
  1007. arc->SetStart( start );
  1008. arc->SetMid( mid );
  1009. arc->SetEnd( end );
  1010. commit.Add( arc );
  1011. }
  1012. else if( source->Type() == PCB_ARC_T )
  1013. {
  1014. PCB_ARC* source_arc = static_cast<PCB_ARC*>( source );
  1015. PCB_SHAPE* arc = new PCB_SHAPE( parent, SHAPE_T::ARC );
  1016. arc->SetFilled( false );
  1017. arc->SetLayer( layer );
  1018. arc->SetWidth( source_arc->GetWidth() );
  1019. arc->SetArcGeometry( source_arc->GetStart(), source_arc->GetMid(), source_arc->GetEnd() );
  1020. commit.Add( arc );
  1021. }
  1022. commit.Push( _( "Create Arc" ) );
  1023. return 0;
  1024. }
  1025. std::optional<SEG> CONVERT_TOOL::getStartEndPoints( EDA_ITEM* aItem )
  1026. {
  1027. switch( aItem->Type() )
  1028. {
  1029. case PCB_SHAPE_T:
  1030. {
  1031. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
  1032. switch( shape->GetShape() )
  1033. {
  1034. case SHAPE_T::SEGMENT:
  1035. case SHAPE_T::ARC:
  1036. case SHAPE_T::POLY:
  1037. case SHAPE_T::BEZIER:
  1038. if( shape->GetStart() == shape->GetEnd() )
  1039. return std::nullopt;
  1040. return std::make_optional<SEG>( VECTOR2I( shape->GetStart() ),
  1041. VECTOR2I( shape->GetEnd() ) );
  1042. default:
  1043. return std::nullopt;
  1044. }
  1045. }
  1046. case PCB_TRACE_T:
  1047. {
  1048. PCB_TRACK* line = static_cast<PCB_TRACK*>( aItem );
  1049. return std::make_optional<SEG>( VECTOR2I( line->GetStart() ), VECTOR2I( line->GetEnd() ) );
  1050. }
  1051. case PCB_ARC_T:
  1052. {
  1053. PCB_ARC* arc = static_cast<PCB_ARC*>( aItem );
  1054. return std::make_optional<SEG>( VECTOR2I( arc->GetStart() ), VECTOR2I( arc->GetEnd() ) );
  1055. }
  1056. default:
  1057. return std::nullopt;
  1058. }
  1059. }
  1060. int CONVERT_TOOL::OutsetItems( const TOOL_EVENT& aEvent )
  1061. {
  1062. PCB_BASE_EDIT_FRAME& frame = *getEditFrame<PCB_BASE_EDIT_FRAME>();
  1063. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  1064. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  1065. {
  1066. // Iterate from the back so we don't have to worry about removals.
  1067. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  1068. {
  1069. BOARD_ITEM* item = aCollector[i];
  1070. // We've converted the polygon and rectangle to segments, so drop everything
  1071. // that isn't a segment at this point
  1072. if( !item->IsType( { PCB_PAD_T, PCB_SHAPE_T } ) )
  1073. {
  1074. aCollector.Remove( item );
  1075. }
  1076. }
  1077. },
  1078. true /* prompt user regarding locked items */ );
  1079. BOARD_COMMIT commit( this );
  1080. for( EDA_ITEM* item : selection )
  1081. item->ClearFlags( STRUCT_DELETED );
  1082. // List of thing to select at the end of the operation
  1083. // (doing it as we go will invalidate the iterator)
  1084. std::vector<BOARD_ITEM*> items_to_select_on_success;
  1085. // Handle modifications to existing items by the routine
  1086. // How to deal with this depends on whether we're in the footprint editor or not
  1087. // and whether the item was conjured up by decomposing a polygon or rectangle
  1088. auto item_modification_handler = [&]( BOARD_ITEM& aItem )
  1089. {
  1090. };
  1091. bool any_items_created = false;
  1092. auto item_creation_handler = [&]( std::unique_ptr<BOARD_ITEM> aItem )
  1093. {
  1094. any_items_created = true;
  1095. items_to_select_on_success.push_back( aItem.get() );
  1096. commit.Add( aItem.release() );
  1097. };
  1098. auto item_removal_handler = [&]( BOARD_ITEM& aItem )
  1099. {
  1100. // If you do an outset on a FP pad, do you really want to delete
  1101. // the parent?
  1102. if( !aItem.GetParentFootprint() )
  1103. {
  1104. commit.Remove( &aItem );
  1105. }
  1106. };
  1107. // Combine these callbacks into a CHANGE_HANDLER to inject in the ROUTINE
  1108. ITEM_MODIFICATION_ROUTINE::CALLABLE_BASED_HANDLER change_handler(
  1109. item_creation_handler, item_modification_handler, item_removal_handler );
  1110. // Persistent settings between dialog invocations
  1111. // Init with some sensible defaults
  1112. static OUTSET_ROUTINE::PARAMETERS outset_params_fp_edit{
  1113. pcbIUScale.mmToIU( 0.25 ), // A common outset value
  1114. false,
  1115. false,
  1116. true,
  1117. F_CrtYd,
  1118. frame.GetDesignSettings().GetLineThickness( F_CrtYd ),
  1119. pcbIUScale.mmToIU( 0.01 ),
  1120. false,
  1121. };
  1122. static OUTSET_ROUTINE::PARAMETERS outset_params_pcb_edit{
  1123. pcbIUScale.mmToIU( 1 ),
  1124. true,
  1125. true,
  1126. true,
  1127. Edge_Cuts, // Outsets often for slots?
  1128. frame.GetDesignSettings().GetLineThickness( Edge_Cuts ),
  1129. std::nullopt,
  1130. false,
  1131. };
  1132. OUTSET_ROUTINE::PARAMETERS& outset_params =
  1133. IsFootprintEditor() ? outset_params_fp_edit : outset_params_pcb_edit;
  1134. {
  1135. DIALOG_OUTSET_ITEMS dlg( frame, outset_params );
  1136. if( dlg.ShowModal() == wxID_CANCEL )
  1137. {
  1138. return 0;
  1139. }
  1140. }
  1141. OUTSET_ROUTINE outset_routine( frame.GetModel(), change_handler, outset_params );
  1142. for( EDA_ITEM* item : selection )
  1143. {
  1144. BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
  1145. outset_routine.ProcessItem( *board_item );
  1146. }
  1147. // Deselect all the original items
  1148. m_selectionTool->ClearSelection();
  1149. // Select added and modified items
  1150. for( BOARD_ITEM* item : items_to_select_on_success )
  1151. m_selectionTool->AddItemToSel( item, true );
  1152. if( any_items_created )
  1153. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1154. // Notify other tools of the changes
  1155. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  1156. commit.Push( outset_routine.GetCommitDescription() );
  1157. if( const std::optional<wxString> msg = outset_routine.GetStatusMessage() )
  1158. frame.ShowInfoBarMsg( *msg );
  1159. return 0;
  1160. }
  1161. void CONVERT_TOOL::setTransitions()
  1162. {
  1163. // clang-format off
  1164. Go( &CONVERT_TOOL::CreatePolys, PCB_ACTIONS::convertToPoly.MakeEvent() );
  1165. Go( &CONVERT_TOOL::CreatePolys, PCB_ACTIONS::convertToZone.MakeEvent() );
  1166. Go( &CONVERT_TOOL::CreatePolys, PCB_ACTIONS::convertToKeepout.MakeEvent() );
  1167. Go( &CONVERT_TOOL::CreateLines, PCB_ACTIONS::convertToLines.MakeEvent() );
  1168. Go( &CONVERT_TOOL::CreateLines, PCB_ACTIONS::convertToTracks.MakeEvent() );
  1169. Go( &CONVERT_TOOL::SegmentToArc, PCB_ACTIONS::convertToArc.MakeEvent() );
  1170. Go( &CONVERT_TOOL::OutsetItems, PCB_ACTIONS::outsetItems.MakeEvent() );
  1171. // clang-format on
  1172. }