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.

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