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.

928 lines
32 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2020 Brian Piccioni brian@documenteddesigns.com
  5. * Copyright (C) 2020-2023 KiCad Developers, see AUTHORS.txt for contributors.
  6. * @author Brian Piccioni <brian@documenteddesigns.com>
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, you may find one here:
  20. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  21. * or you may search the http://www.gnu.org website for the version 2 license,
  22. * or you may write to the Free Software Foundation, Inc.,
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25. #include <algorithm>
  26. #include <base_units.h>
  27. #include <bitmaps.h>
  28. #include <board_commit.h>
  29. #include <confirm.h>
  30. #include <ctype.h>
  31. #include <dialog_board_reannotate.h>
  32. #include <string_utils.h> // StrNumCmp
  33. #include <kiface_base.h>
  34. #include <pcbnew_settings.h>
  35. #include <refdes_utils.h>
  36. #include <tool/grid_menu.h>
  37. #include <widgets/wx_html_report_panel.h>
  38. #include <wx/valtext.h>
  39. bool SortYFirst;
  40. bool DescendingFirst;
  41. bool DescendingSecond;
  42. //
  43. // This converts the index into a sort code. Note that Back sort code will have left and
  44. // right swapped.
  45. //
  46. int FrontDirectionsArray[] = {
  47. SORTYFIRST + ASCENDINGFIRST + ASCENDINGSECOND, // "Top to bottom, left to right", // 100
  48. SORTYFIRST + ASCENDINGFIRST + DESCENDINGSECOND, // "Top to bottom, right to left", // 101
  49. SORTYFIRST + DESCENDINGFIRST + ASCENDINGSECOND, // "Back to Front, left to right", // 110
  50. SORTYFIRST + DESCENDINGFIRST + DESCENDINGSECOND, // "Back to Front, right to left", // 111
  51. SORTXFIRST + ASCENDINGFIRST + ASCENDINGSECOND, // "Left to right, Front to Back", // 000
  52. SORTXFIRST + ASCENDINGFIRST + DESCENDINGSECOND, // "Left to right, Back to Front", // 001
  53. SORTXFIRST + DESCENDINGFIRST + ASCENDINGSECOND, // "Right to left, Front to Back", // 010
  54. SORTXFIRST + DESCENDINGFIRST + DESCENDINGSECOND // "Right to left, Back to Front", // 011
  55. };
  56. //
  57. // Back Left/Right is opposite because it is a mirror image (coordinates are from the top)
  58. //
  59. int BackDirectionsArray[] = {
  60. SORTYFIRST + ASCENDINGFIRST + DESCENDINGSECOND, // "Top to bottom, left to right", // 101
  61. SORTYFIRST + ASCENDINGFIRST + ASCENDINGSECOND, // "Top to bottom, right to left", // 100
  62. SORTYFIRST + DESCENDINGFIRST + DESCENDINGSECOND, // "Bottom to top, left to right", // 111
  63. SORTYFIRST + DESCENDINGFIRST + ASCENDINGSECOND, // "Bottom to top, right to left", // 110
  64. SORTXFIRST + DESCENDINGFIRST + ASCENDINGSECOND, // "Left to right, top to bottom", // 010
  65. SORTXFIRST + DESCENDINGFIRST + DESCENDINGSECOND, // "Left to right, bottom to top", // 011
  66. SORTXFIRST + ASCENDINGFIRST + ASCENDINGSECOND, // "Right to left, top to bottom", // 000
  67. SORTXFIRST + ASCENDINGFIRST + DESCENDINGSECOND // "Right to left, bottom to top", // 001
  68. };
  69. #define SetSortCodes( DirArray, Code ) \
  70. { \
  71. SortYFirst = ( ( DirArray[Code] & SORTYFIRST ) != 0 ); \
  72. DescendingFirst = ( ( DirArray[Code] & DESCENDINGFIRST ) != 0 ); \
  73. DescendingSecond = ( ( DirArray[Code] & DESCENDINGSECOND ) != 0 ); \
  74. }
  75. wxString AnnotateString[] = {
  76. _( "All" ), // ANNOTATE_ALL
  77. _( "Only front" ), // AnnotateFront
  78. _( "Only back" ), // AnnotateBack
  79. _( "Only selected" ) // ANNOTATE_SELECTED
  80. };
  81. wxString ActionMessage[] = {
  82. "", // UPDATE_REFDES
  83. _( "Empty" ), // EMPTY_REFDES
  84. _( "Invalid" ), // INVALID_REFDES
  85. _( "Excluded" ) // EXCLUDE_REFDES
  86. };
  87. DIALOG_BOARD_REANNOTATE::DIALOG_BOARD_REANNOTATE( PCB_EDIT_FRAME* aParentFrame )
  88. : DIALOG_BOARD_REANNOTATE_BASE( aParentFrame ),
  89. m_footprints( aParentFrame->GetBoard()->Footprints() )
  90. {
  91. m_frame = aParentFrame;
  92. m_Config = Kiface().KifaceSettings();
  93. InitValues();
  94. // Init bitmaps associated to some wxRadioButton
  95. reannotate_down_right_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_right_down ) );
  96. reannotate_right_down_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_left_down ) );
  97. reannotate_down_left_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_right_up ) );
  98. reannotate_left_down_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_left_up ) );
  99. reannotate_up_right_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_down_left ) );
  100. reannotate_right_up_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_up_left ) );
  101. reannotate_up_left_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_down_right ) );
  102. reannotate_left_up_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_up_right ) );
  103. m_FrontRefDesStart->SetValidator( wxTextValidator( wxFILTER_DIGITS ) );
  104. m_BackRefDesStart->SetValidator( wxTextValidator( wxFILTER_DIGITS ) );
  105. m_sdbSizerOK->SetLabel( _( "Reannotate PCB" ) );
  106. m_sdbSizerCancel->SetLabel( _( "Close" ) );
  107. m_sdbSizer->Layout();
  108. m_settings = aParentFrame->config();
  109. wxArrayString gridslist;
  110. GRID_MENU::BuildChoiceList( &gridslist, m_settings, aParentFrame );
  111. if( -1 == m_gridIndex ) // If no default loaded
  112. m_gridIndex = m_settings->m_Window.grid.last_size_idx; // Get the current grid size
  113. m_sortGridx = m_frame->GetCanvas()->GetGAL()->GetGridSize().x;
  114. m_sortGridy = m_frame->GetCanvas()->GetGAL()->GetGridSize().y;
  115. m_GridChoice->Set( gridslist );
  116. m_GridChoice->SetSelection( m_gridIndex );
  117. // Ensure m_sortCode is a valid value (0 .. m_sortButtons.size()-1)
  118. m_sortCode = std::max( 0, m_sortCode );
  119. m_sortCode = std::min( m_sortCode, (int)m_sortButtons.size()-1 );
  120. for( wxRadioButton* button : m_sortButtons )
  121. button->SetValue( false );
  122. m_selection = m_frame->GetToolManager()->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
  123. if( !m_selection.Empty() )
  124. m_annotationScope = ANNOTATE_SELECTED;
  125. // Ensure m_annotationScope is a valid value (0 .. m_scopeRadioButtons.size()-1)
  126. m_annotationScope = std::max( 0, m_annotationScope );
  127. m_annotationScope = std::min( m_annotationScope, (int)m_scopeRadioButtons.size()-1 );
  128. for( wxRadioButton* button : m_scopeRadioButtons )
  129. button->SetValue( false );
  130. m_scopeRadioButtons.at( m_annotationScope )->SetValue( true );
  131. m_sortButtons.at( m_sortCode )->SetValue( true );
  132. m_ExcludeList->SetToolTip( m_ExcludeListText->GetToolTipText() );
  133. m_GridChoice->SetToolTip( m_SortGridText->GetToolTipText() );
  134. m_MessageWindow->SetFileName( Prj().GetProjectPath() + wxT( "report.txt" ) );
  135. finishDialogSettings();
  136. }
  137. DIALOG_BOARD_REANNOTATE::~DIALOG_BOARD_REANNOTATE()
  138. {
  139. GetParameters(); // Get the current menu settings
  140. PCBNEW_SETTINGS* cfg = m_frame->GetPcbNewSettings();
  141. cfg->m_Reannotate.sort_on_fp_location = m_locationChoice->GetSelection() == 0;
  142. cfg->m_Reannotate.remove_front_prefix = m_RemoveFrontPrefix->GetValue();
  143. cfg->m_Reannotate.remove_back_prefix = m_RemoveBackPrefix->GetValue();
  144. cfg->m_Reannotate.exclude_locked = m_ExcludeLocked->GetValue();
  145. cfg->m_Reannotate.grid_index = m_gridIndex;
  146. cfg->m_Reannotate.sort_code = m_sortCode;
  147. cfg->m_Reannotate.annotation_choice = m_annotationScope;
  148. cfg->m_Reannotate.report_severity = m_severity;
  149. cfg->m_Reannotate.front_refdes_start = m_FrontRefDesStart->GetValue();
  150. cfg->m_Reannotate.back_refdes_start = m_BackRefDesStart->GetValue();
  151. cfg->m_Reannotate.front_prefix = m_FrontPrefix->GetValue();
  152. cfg->m_Reannotate.back_prefix = m_BackPrefix->GetValue();
  153. cfg->m_Reannotate.exclude_list = m_ExcludeList->GetValue();
  154. cfg->m_Reannotate.report_file_name = m_MessageWindow->GetFileName();
  155. }
  156. void DIALOG_BOARD_REANNOTATE::InitValues( void )
  157. {
  158. PCBNEW_SETTINGS* cfg = m_frame->GetPcbNewSettings();
  159. m_locationChoice->SetSelection( cfg->m_Reannotate.sort_on_fp_location ? 0 : 1 );
  160. m_RemoveFrontPrefix->SetValue( cfg->m_Reannotate.remove_front_prefix );
  161. m_RemoveBackPrefix->SetValue( cfg->m_Reannotate.remove_back_prefix );
  162. m_ExcludeLocked->SetValue( cfg->m_Reannotate.exclude_locked );
  163. m_gridIndex = cfg->m_Reannotate.grid_index ;
  164. m_sortCode = cfg->m_Reannotate.sort_code ;
  165. m_annotationScope = cfg->m_Reannotate.annotation_choice ;
  166. m_severity = cfg->m_Reannotate.report_severity;
  167. m_FrontRefDesStart->SetValue( cfg->m_Reannotate.front_refdes_start );
  168. m_BackRefDesStart->SetValue( cfg->m_Reannotate.back_refdes_start );
  169. m_FrontPrefix->SetValue( cfg->m_Reannotate.front_prefix );
  170. m_BackPrefix->SetValue( cfg->m_Reannotate.back_prefix );
  171. m_ExcludeList->SetValue( cfg->m_Reannotate.exclude_list );
  172. m_MessageWindow->SetFileName( cfg->m_Reannotate.report_file_name );
  173. }
  174. void DIALOG_BOARD_REANNOTATE::OnCloseClick( wxCommandEvent& event )
  175. {
  176. EndDialog( wxID_OK );
  177. }
  178. void DIALOG_BOARD_REANNOTATE::FilterPrefix( wxTextCtrl* aPrefix )
  179. {
  180. std::string tmps = VALIDPREFIX;
  181. if( aPrefix->GetValue().empty() )
  182. return; //Should never happen
  183. char lastc = aPrefix->GetValue().Last();
  184. if( isalnum( (int) lastc ) )
  185. return;
  186. if( std::string::npos != tmps.find( lastc ) )
  187. return;
  188. tmps = aPrefix->GetValue();
  189. aPrefix->Clear();
  190. tmps.pop_back();
  191. aPrefix->AppendText( tmps );
  192. }
  193. REFDES_TYPE_STR* DIALOG_BOARD_REANNOTATE::GetOrBuildRefDesInfo( const wxString& aRefDesPrefix,
  194. unsigned int aStartRefDes )
  195. {
  196. unsigned int requiredLastRef = ( aStartRefDes == 0 ? 1 : aStartRefDes ) - 1;
  197. for( size_t i = 0; i < m_refDesTypes.size(); i++ ) // See if it is in the types array
  198. {
  199. if( m_refDesTypes[i].RefDesType == aRefDesPrefix ) // Found it!
  200. {
  201. m_refDesTypes[i].LastUsedRefDes = std::max( m_refDesTypes[i].LastUsedRefDes,
  202. requiredLastRef );
  203. return &m_refDesTypes[i];
  204. }
  205. }
  206. // Wasn't in the types array so add it
  207. REFDES_TYPE_STR newtype;
  208. newtype.RefDesType = aRefDesPrefix;
  209. newtype.LastUsedRefDes = requiredLastRef;
  210. m_refDesTypes.push_back( newtype );
  211. return &m_refDesTypes.back();
  212. }
  213. void DIALOG_BOARD_REANNOTATE::FilterFrontPrefix( wxCommandEvent& event )
  214. {
  215. FilterPrefix( m_FrontPrefix );
  216. }
  217. void DIALOG_BOARD_REANNOTATE::FilterBackPrefix( wxCommandEvent& event )
  218. {
  219. FilterPrefix( m_BackPrefix );
  220. }
  221. void DIALOG_BOARD_REANNOTATE::OnApplyClick( wxCommandEvent& event )
  222. {
  223. wxString warning;
  224. if( m_frame->GetBoard()->IsEmpty() )
  225. {
  226. ShowReport( _( "No PCB to reannotate!" ), RPT_SEVERITY_ERROR );
  227. return;
  228. }
  229. GetParameters(); // Figure out how this is to be done
  230. MakeSampleText( warning );
  231. if( !IsOK( m_frame, warning ) )
  232. return;
  233. if( ReannotateBoard() )
  234. {
  235. ShowReport( _( "PCB successfully reannotated" ), RPT_SEVERITY_ACTION );
  236. ShowReport( _( "PCB annotation changes should be synchronized with schematic using "
  237. "the \"Update Schematic from PCB\" tool." ), RPT_SEVERITY_WARNING );
  238. }
  239. m_MessageWindow->SetLazyUpdate( false );
  240. m_MessageWindow->Flush( false );
  241. m_frame->GetCanvas()->Refresh(); // Redraw
  242. m_frame->OnModify(); // Need to save file on exit.
  243. }
  244. void DIALOG_BOARD_REANNOTATE::MakeSampleText( wxString& aMessage )
  245. {
  246. aMessage.Printf( _( "\n%s footprints will be reannotated." ),
  247. _( AnnotateString[m_annotationScope] ) );
  248. if( !m_ExcludeList->GetValue().empty() )
  249. {
  250. aMessage += wxString::Format( _( "\nAny reference types %s will not be annotated." ),
  251. m_ExcludeList->GetValue() );
  252. }
  253. if( m_ExcludeLocked->GetValue() )
  254. aMessage += wxString::Format( _( "\nLocked footprints will not be annotated" ) );
  255. if( !m_AnnotateBack->GetValue() )
  256. {
  257. aMessage += wxString::Format( _( "\nFront footprints will start at %s" ),
  258. m_FrontRefDesStart->GetValue() );
  259. }
  260. if( !m_AnnotateFront->GetValue() )
  261. {
  262. bool frontPlusOne = ( 0 == wxAtoi( m_BackRefDesStart->GetValue() ) )
  263. && !m_AnnotateBack->GetValue();
  264. aMessage += wxString::Format( _( "\nBack footprints will start at %s." ),
  265. frontPlusOne ? _( "the last front footprint + 1" ) :
  266. m_BackRefDesStart->GetValue() );
  267. }
  268. if( !m_FrontPrefix->GetValue().empty() )
  269. {
  270. if( m_RemoveFrontPrefix->GetValue() )
  271. {
  272. aMessage += wxString::Format( _( "\nFront footprints starting with '%s' will have "
  273. "the prefix removed." ),
  274. m_FrontPrefix->GetValue() );
  275. }
  276. else
  277. {
  278. aMessage += wxString::Format( _( "\nFront footprints will have '%s' inserted as a "
  279. "prefix." ),
  280. m_FrontPrefix->GetValue() );
  281. }
  282. }
  283. if( !m_BackPrefix->GetValue().empty() )
  284. {
  285. if( m_RemoveBackPrefix->GetValue() )
  286. {
  287. aMessage += wxString::Format( _( "\nBack footprints starting with '%s' will have the "
  288. "prefix removed." ),
  289. m_BackPrefix->GetValue() );
  290. }
  291. else
  292. {
  293. aMessage += wxString::Format( _( "\nBack footprints will have '%s' inserted as a "
  294. "prefix." ),
  295. m_BackPrefix->GetValue() );
  296. }
  297. }
  298. bool fpLocation = m_locationChoice->GetSelection() == 0;
  299. aMessage += wxString::Format( _( "\nPrior to sorting by %s, the coordinates of which will be "
  300. "rounded to a %s, %s grid." ),
  301. fpLocation ? _( "footprint location" )
  302. : _( "reference designator location" ),
  303. m_frame->MessageTextFromValue( m_sortGridx ),
  304. m_frame->MessageTextFromValue( m_sortGridy ) );
  305. ShowReport( aMessage, RPT_SEVERITY_INFO );
  306. }
  307. void DIALOG_BOARD_REANNOTATE::GetParameters()
  308. {
  309. m_sortCode = 0; // Convert radio button to sort direction code
  310. for( wxRadioButton* sortbuttons : m_sortButtons )
  311. {
  312. if( sortbuttons->GetValue() )
  313. break;
  314. m_sortCode++;
  315. }
  316. if( m_sortCode >= (int) m_sortButtons.size() )
  317. m_sortCode = 0;
  318. m_frontPrefixString = m_FrontPrefix->GetValue();
  319. m_backPrefixString = m_BackPrefix->GetValue();
  320. // Get the chosen sort grid for rounding
  321. m_gridIndex = m_GridChoice->GetSelection();
  322. if( m_gridIndex >= ( int ) m_settings->m_Window.grid.sizes.size() )
  323. {
  324. m_sortGridx = EDA_UNIT_UTILS::UI::DoubleValueFromString(
  325. pcbIUScale, EDA_UNITS::MILS, m_settings->m_Window.grid.user_grid_x );
  326. m_sortGridy = EDA_UNIT_UTILS::UI::DoubleValueFromString(
  327. pcbIUScale, EDA_UNITS::MILS, m_settings->m_Window.grid.user_grid_y );
  328. }
  329. else
  330. {
  331. m_sortGridx = EDA_UNIT_UTILS::UI::DoubleValueFromString(
  332. pcbIUScale, EDA_UNITS::MILS, m_settings->m_Window.grid.sizes[m_gridIndex] );
  333. m_sortGridy = m_sortGridx;
  334. }
  335. m_annotationScope = ANNOTATE_ALL;
  336. for( wxRadioButton* button : m_scopeRadioButtons )
  337. {
  338. if( button->GetValue() )
  339. break;
  340. else
  341. m_annotationScope++;
  342. }
  343. // Ensure m_annotationScope value is valid
  344. if( m_annotationScope >= (int)m_scopeRadioButtons.size() )
  345. m_annotationScope = ANNOTATE_ALL;
  346. m_MessageWindow->SetLazyUpdate( true );
  347. }
  348. int DIALOG_BOARD_REANNOTATE::RoundToGrid( int aCoord, int aGrid )
  349. {
  350. if( 0 == aGrid )
  351. aGrid = MINGRID;
  352. int rounder;
  353. rounder = aCoord % aGrid;
  354. aCoord -= rounder;
  355. if( abs( rounder ) > ( aGrid / 2 ) )
  356. aCoord += ( aCoord < 0 ? -aGrid : aGrid );
  357. return ( aCoord );
  358. }
  359. /// Compare function used to compare ChangeArray element for sort
  360. /// @return true is A < B
  361. static bool ChangeArrayCompare( const REFDES_CHANGE& aA, const REFDES_CHANGE& aB )
  362. {
  363. return ( StrNumCmp( aA.OldRefDesString, aB.OldRefDesString ) < 0 );
  364. }
  365. /// Compare function to sort footprints.
  366. /// @return true if the first coordinate should be before the second coordinate
  367. static bool ModuleCompare( const REFDES_INFO& aA, const REFDES_INFO& aB )
  368. {
  369. int X0 = aA.roundedx, X1 = aB.roundedx, Y0 = aA.roundedy, Y1 = aB.roundedy;
  370. if( SortYFirst ) //If sorting by Y then X, swap X and Y
  371. {
  372. std::swap( X0, Y0 );
  373. std::swap( X1, Y1 );
  374. }
  375. // If descending, same compare just swap directions
  376. if( DescendingFirst )
  377. std::swap( X0, X1 );
  378. if( DescendingSecond )
  379. std::swap( Y0, Y1 );
  380. if( X0 < X1 )
  381. return ( true ); // yes, its smaller
  382. if( X0 > X1 )
  383. return ( false ); // No its not
  384. if( Y0 < Y1 )
  385. return ( true ); // same but equal
  386. return ( false );
  387. }
  388. wxString DIALOG_BOARD_REANNOTATE::CoordTowxString( int aX, int aY )
  389. {
  390. return wxString::Format( wxT( "%s, %s" ),
  391. m_frame->MessageTextFromValue( aX ),
  392. m_frame->MessageTextFromValue( aY ) );
  393. }
  394. void DIALOG_BOARD_REANNOTATE::ShowReport( const wxString& aMessage, SEVERITY aSeverity )
  395. {
  396. wxStringTokenizer msgs( aMessage, wxT( "\n" ) );
  397. while( msgs.HasMoreTokens() )
  398. {
  399. m_MessageWindow->Report( msgs.GetNextToken(), aSeverity );
  400. }
  401. }
  402. void DIALOG_BOARD_REANNOTATE::LogChangePlan()
  403. {
  404. int i = 1;
  405. wxString message;
  406. message.Printf( _( "\n\nThere are %i types of reference designations\n"
  407. "**********************************************************\n" ),
  408. (int) m_refDesTypes.size() );
  409. for( REFDES_TYPE_STR Type : m_refDesTypes ) // Show all the types of refdes
  410. message += Type.RefDesType + ( 0 == ( i++ % 16 ) ? wxT( "\n" ) : wxS( " " ) );
  411. if( !m_excludeArray.empty() )
  412. {
  413. wxString excludes;
  414. for( wxString& exclude : m_excludeArray ) // Show the refdes we are excluding
  415. excludes += exclude + wxS( " " );
  416. message += wxString::Format( _( "\nExcluding: %s from reannotation\n\n" ), excludes );
  417. }
  418. message += _( "\n Change Array\n***********************\n" );
  419. for( const REFDES_CHANGE& change : m_changeArray )
  420. {
  421. message += wxString::Format( wxT( "%s -> %s %s %s\n" ),
  422. change.OldRefDesString,
  423. change.NewRefDes,
  424. ActionMessage[change.Action],
  425. UPDATE_REFDES != change.Action ? _( " will be ignored" )
  426. : wxString( wxT( "" ) ) );
  427. }
  428. ShowReport( message, RPT_SEVERITY_INFO );
  429. }
  430. void DIALOG_BOARD_REANNOTATE::LogFootprints( const wxString& aMessage,
  431. const std::vector<REFDES_INFO>& aFootprints )
  432. {
  433. wxString message = aMessage;
  434. if( aFootprints.empty() )
  435. message += _( "\nNo footprints" );
  436. else
  437. {
  438. int i = 1;
  439. bool fpLocations = m_locationChoice->GetSelection() == 0;
  440. message += wxString::Format( _( "\n*********** Sort on %s ***********" ),
  441. fpLocations ? _( "Footprint Coordinates" )
  442. : _( "Reference Designator Coordinates" ) );
  443. message += wxString::Format( _( "\nSort Code %d" ), m_sortCode );
  444. for( const REFDES_INFO& mod : aFootprints )
  445. {
  446. message += wxString::Format( _( "\n%d %s UUID: [%s], X, Y: %s, Rounded X, Y, %s" ),
  447. i++,
  448. mod.RefDesString,
  449. mod.Uuid.AsString(),
  450. CoordTowxString( mod.x, mod.y ),
  451. CoordTowxString( mod.roundedx, mod.roundedy ) );
  452. }
  453. }
  454. ShowReport( message, RPT_SEVERITY_INFO );
  455. }
  456. bool DIALOG_BOARD_REANNOTATE::ReannotateBoard()
  457. {
  458. std::vector<REFDES_INFO> BadRefDes;
  459. wxString message, badrefdes;
  460. STRING_FORMATTER stringformatter;
  461. REFDES_CHANGE* newref;
  462. NETLIST netlist;
  463. if( !BuildFootprintList( BadRefDes ) )
  464. {
  465. ShowReport( _( "Selected options resulted in errors! Change them and try again." ),
  466. RPT_SEVERITY_ERROR );
  467. return false;
  468. }
  469. if( !BadRefDes.empty() )
  470. {
  471. message.Printf( _( "\nPCB has %d empty or invalid reference designations."
  472. "\nRecommend running DRC with 'Test for parity between PCB and "
  473. "schematic' checked.\n" ),
  474. (int) BadRefDes.size() );
  475. for( const REFDES_INFO& mod : BadRefDes )
  476. {
  477. badrefdes += wxString::Format( _( "\nRefDes: %s Footprint: %s:%s at %s on PCB." ),
  478. mod.RefDesString,
  479. mod.FPID.GetLibNickname().wx_str(),
  480. mod.FPID.GetLibItemName().wx_str(),
  481. CoordTowxString( mod.x, mod.y ) );
  482. }
  483. ShowReport( message + badrefdes + wxT( "\n" ), RPT_SEVERITY_WARNING );
  484. message += _( "Reannotate anyway?" );
  485. if( !IsOK( m_frame, message ) )
  486. return false;
  487. }
  488. BOARD_COMMIT commit( m_frame );
  489. for( FOOTPRINT* footprint : m_footprints )
  490. {
  491. newref = GetNewRefDes( footprint );
  492. if( nullptr == newref )
  493. return false;
  494. commit.Modify( footprint ); // Make a copy for undo
  495. footprint->SetReference( newref->NewRefDes ); // Update the PCB reference
  496. m_frame->GetCanvas()->GetView()->Update( footprint ); // Touch the footprint
  497. }
  498. commit.Push( wxT( "Geographic reannotation" ) );
  499. return true;
  500. }
  501. bool DIALOG_BOARD_REANNOTATE::BuildFootprintList( std::vector<REFDES_INFO>& aBadRefDes )
  502. {
  503. bool annotateSelected = m_AnnotateSelection->GetValue();
  504. bool annotateFront = m_AnnotateFront->GetValue(); // Unless only doing back
  505. bool annotateBack = m_AnnotateBack->GetValue(); // Unless only doing front
  506. bool skipLocked = m_ExcludeLocked->GetValue();
  507. int errorcount = 0;
  508. size_t firstnum = 0;
  509. m_frontFootprints.clear();
  510. m_backFootprints.clear();
  511. m_excludeArray.clear();
  512. m_footprints = m_frame->GetBoard()->Footprints();
  513. std::vector<KIID> selected;
  514. if( annotateSelected )
  515. {
  516. for( EDA_ITEM* item : m_selection )
  517. {
  518. // Get the timestamps of selected footprints
  519. if( item->Type() == PCB_FOOTPRINT_T )
  520. selected.push_back( item->m_Uuid );
  521. }
  522. }
  523. wxString exclude;
  524. // Break exclude list into words.
  525. for( auto thischar : m_ExcludeList->GetValue() )
  526. {
  527. if( ( ' ' == thischar ) || ( ',' == thischar ) )
  528. {
  529. m_excludeArray.push_back( exclude );
  530. exclude.clear();
  531. }
  532. else
  533. exclude += thischar;
  534. if( !exclude.empty() )
  535. m_excludeArray.push_back( exclude );
  536. }
  537. REFDES_INFO fpData;
  538. bool useModuleLocation = m_locationChoice->GetSelection() == 0;
  539. for( FOOTPRINT* footprint : m_footprints )
  540. {
  541. fpData.Uuid = footprint->m_Uuid;
  542. fpData.RefDesString = footprint->GetReference();
  543. fpData.FPID = footprint->GetFPID();
  544. fpData.x = useModuleLocation ? footprint->GetPosition().x
  545. : footprint->Reference().GetPosition().x;
  546. fpData.y = useModuleLocation ? footprint->GetPosition().y
  547. : footprint->Reference().GetPosition().y;
  548. fpData.roundedx = RoundToGrid( fpData.x, m_sortGridx ); // Round to sort
  549. fpData.roundedy = RoundToGrid( fpData.y, m_sortGridy );
  550. fpData.Front = footprint->GetLayer() == F_Cu;
  551. fpData.Action = UPDATE_REFDES; // Usually good
  552. if( fpData.RefDesString.IsEmpty() )
  553. {
  554. fpData.Action = EMPTY_REFDES;
  555. }
  556. else
  557. {
  558. firstnum = fpData.RefDesString.find_first_of( wxT( "0123456789" ) );
  559. if( std::string::npos == firstnum )
  560. fpData.Action = INVALID_REFDES; // do not change ref des such as 12 or +1, or L
  561. }
  562. // Get the type (R, C, etc)
  563. fpData.RefDesType = fpData.RefDesString.substr( 0, firstnum );
  564. for( wxString excluded : m_excludeArray )
  565. {
  566. if( excluded == fpData.RefDesType ) // Am I supposed to exclude this type?
  567. {
  568. fpData.Action = EXCLUDE_REFDES; // Yes
  569. break;
  570. }
  571. }
  572. if(( fpData.Front && annotateBack ) || // If a front fp and doing backs only
  573. ( !fpData.Front && annotateFront ) || // If a back fp and doing front only
  574. ( footprint->IsLocked() && skipLocked ) ) // If excluding locked and it is locked
  575. {
  576. fpData.Action = EXCLUDE_REFDES;
  577. }
  578. if( annotateSelected )
  579. { // If only annotating selected c
  580. fpData.Action = EXCLUDE_REFDES; // Assume it isn't selected
  581. for( KIID sel : selected )
  582. {
  583. if( fpData.Uuid == sel )
  584. { // Found in selected footprints
  585. fpData.Action = UPDATE_REFDES; // Update it
  586. break;
  587. }
  588. }
  589. }
  590. if( fpData.Front )
  591. m_frontFootprints.push_back( fpData );
  592. else
  593. m_backFootprints.push_back( fpData );
  594. }
  595. // Determine the sort order for the front.
  596. SetSortCodes( FrontDirectionsArray, m_sortCode );
  597. // Sort the front footprints.
  598. sort( m_frontFootprints.begin(), m_frontFootprints.end(), ModuleCompare );
  599. // Determine the sort order for the back.
  600. SetSortCodes( BackDirectionsArray, m_sortCode );
  601. // Sort the back footprints.
  602. sort( m_backFootprints.begin(), m_backFootprints.end(), ModuleCompare );
  603. m_refDesTypes.clear();
  604. m_changeArray.clear();
  605. BuildUnavailableRefsList();
  606. if( !m_frontFootprints.empty() )
  607. {
  608. BuildChangeArray( m_frontFootprints, wxAtoi( m_FrontRefDesStart->GetValue() ),
  609. m_FrontPrefix->GetValue(), m_RemoveFrontPrefix->GetValue(), aBadRefDes );
  610. }
  611. if( !m_backFootprints.empty() )
  612. {
  613. BuildChangeArray( m_backFootprints, wxAtoi( m_BackRefDesStart->GetValue() ),
  614. m_BackPrefix->GetValue(), m_RemoveBackPrefix->GetValue(), aBadRefDes );
  615. }
  616. if( !m_changeArray.empty() )
  617. sort( m_changeArray.begin(), m_changeArray.end(), ChangeArrayCompare );
  618. LogChangePlan();
  619. size_t changearraysize = m_changeArray.size();
  620. for( size_t i = 0; i < changearraysize; i++ ) // Scan through for duplicates if update or skip
  621. {
  622. if( ( m_changeArray[i].Action != EMPTY_REFDES )
  623. && ( m_changeArray[i].Action != INVALID_REFDES ) )
  624. {
  625. for( size_t j = i + 1; j < changearraysize; j++ )
  626. {
  627. if( m_changeArray[i].NewRefDes == m_changeArray[j].NewRefDes )
  628. {
  629. ShowReport( wxString::Format( _( "Duplicate instances of %s" ),
  630. m_changeArray[j].NewRefDes ),
  631. RPT_SEVERITY_ERROR );
  632. if( errorcount++ > MAXERROR )
  633. {
  634. ShowReport( _( "Aborted: too many errors" ), RPT_SEVERITY_ERROR );
  635. break;
  636. }
  637. }
  638. }
  639. }
  640. if( errorcount > MAXERROR )
  641. break;
  642. }
  643. return ( 0 == errorcount );
  644. }
  645. void DIALOG_BOARD_REANNOTATE::BuildUnavailableRefsList()
  646. {
  647. std::vector<REFDES_INFO> excludedFootprints;
  648. for( REFDES_INFO fpData : m_frontFootprints )
  649. {
  650. if( fpData.Action == EXCLUDE_REFDES )
  651. excludedFootprints.push_back( fpData );
  652. }
  653. for( REFDES_INFO fpData : m_backFootprints )
  654. {
  655. if( fpData.Action == EXCLUDE_REFDES )
  656. excludedFootprints.push_back( fpData );
  657. }
  658. for( REFDES_INFO fpData : excludedFootprints )
  659. {
  660. if( fpData.Action == EXCLUDE_REFDES )
  661. {
  662. REFDES_TYPE_STR* refDesInfo = GetOrBuildRefDesInfo( fpData.RefDesType );
  663. refDesInfo->UnavailableRefs.insert( UTIL::GetRefDesNumber( fpData.RefDesString ) );
  664. }
  665. }
  666. }
  667. void DIALOG_BOARD_REANNOTATE::BuildChangeArray( std::vector<REFDES_INFO>& aFootprints,
  668. unsigned int aStartRefDes, const wxString& aPrefix,
  669. bool aRemovePrefix,
  670. std::vector<REFDES_INFO>& aBadRefDes )
  671. {
  672. size_t prefixsize = aPrefix.size();
  673. bool haveprefix = ( 0 != prefixsize ); // Do I have a prefix?
  674. bool addprefix = haveprefix & !aRemovePrefix; // Yes- and I'm not removing it
  675. aRemovePrefix &= haveprefix; // Only remove if I have a prefix
  676. bool prefixpresent; // Prefix found
  677. wxString logstring = ( aFootprints.front().Front ) ? _( "\n\nFront Footprints" )
  678. : _( "\n\nBack Footprints" );
  679. LogFootprints( logstring, aFootprints );
  680. if( 0 != aStartRefDes ) // Initialize the change array if present
  681. {
  682. for( size_t i = 0; i < m_refDesTypes.size(); i++ )
  683. m_refDesTypes[i].LastUsedRefDes = aStartRefDes;
  684. }
  685. for( REFDES_INFO fpData : aFootprints )
  686. {
  687. REFDES_CHANGE change;
  688. change.Uuid = fpData.Uuid;
  689. change.Action = fpData.Action;
  690. change.OldRefDesString = fpData.RefDesString;
  691. change.NewRefDes = fpData.RefDesString;
  692. change.Front = fpData.Front;
  693. if( fpData.RefDesString.IsEmpty() )
  694. fpData.Action = EMPTY_REFDES;
  695. if( ( change.Action == EMPTY_REFDES ) || ( change.Action == INVALID_REFDES ) )
  696. {
  697. m_changeArray.push_back( change );
  698. aBadRefDes.push_back( fpData );
  699. continue;
  700. }
  701. if( change.Action == UPDATE_REFDES )
  702. {
  703. prefixpresent = ( 0 == fpData.RefDesType.find( aPrefix ) );
  704. if( addprefix && !prefixpresent )
  705. fpData.RefDesType.insert( 0, aPrefix ); // Add prefix once only
  706. if( aRemovePrefix && prefixpresent ) // If there is a prefix remove it
  707. fpData.RefDesType.erase( 0, prefixsize );
  708. REFDES_TYPE_STR* refDesInfo = GetOrBuildRefDesInfo( fpData.RefDesType, aStartRefDes );
  709. unsigned int newRefDesNumber = refDesInfo->LastUsedRefDes + 1;
  710. while( refDesInfo->UnavailableRefs.count( newRefDesNumber ) )
  711. newRefDesNumber++;
  712. change.NewRefDes = refDesInfo->RefDesType + std::to_string( newRefDesNumber );
  713. refDesInfo->LastUsedRefDes = newRefDesNumber;
  714. }
  715. m_changeArray.push_back( change );
  716. }
  717. }
  718. REFDES_CHANGE* DIALOG_BOARD_REANNOTATE::GetNewRefDes( FOOTPRINT* aFootprint )
  719. {
  720. size_t i;
  721. for( i = 0; i < m_changeArray.size(); i++ )
  722. {
  723. if( aFootprint->m_Uuid == m_changeArray[i].Uuid )
  724. return ( &m_changeArray[i] );
  725. }
  726. ShowReport( _( "Footprint not found in changelist" ) + wxS( " " ) + aFootprint->GetReference(),
  727. RPT_SEVERITY_ERROR );
  728. return nullptr; // Should never happen
  729. }