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.

931 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" ), // 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. // 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 = AnnotateSelected;
  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. RefDesTypeStr* 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. RefDesTypeStr 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 = AnnotateAll;
  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 = AnnotateAll;
  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 RefDesChange& aA, const RefDesChange& 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 RefDesInfo& aA, const RefDesInfo& 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. size_t pos = 0, prev = 0;
  397. do
  398. {
  399. pos = aMessage.ToStdString().find( '\n', prev );
  400. m_MessageWindow->Report( aMessage.ToStdString().substr( prev, pos - prev ), aSeverity );
  401. prev = pos + 1;
  402. } while( std::string::npos != pos );
  403. }
  404. void DIALOG_BOARD_REANNOTATE::LogChangePlan()
  405. {
  406. int i = 1;
  407. wxString message;
  408. message.Printf( _( "\n\nThere are %i types of reference designations\n"
  409. "**********************************************************\n" ),
  410. (int) m_refDesTypes.size() );
  411. for( RefDesTypeStr Type : m_refDesTypes ) // Show all the types of refdes
  412. message += Type.RefDesType + ( 0 == ( i++ % 16 ) ? wxT( "\n" ) : wxS( " " ) );
  413. if( !m_excludeArray.empty() )
  414. {
  415. wxString excludes;
  416. for( wxString& exclude : m_excludeArray ) // Show the refdes we are excluding
  417. excludes += exclude + wxS( " " );
  418. message += wxString::Format( _( "\nExcluding: %s from reannotation\n\n" ), excludes );
  419. }
  420. message += _( "\n Change Array\n***********************\n" );
  421. for( const RefDesChange& change : m_changeArray )
  422. {
  423. message += wxString::Format( wxT( "%s -> %s %s %s\n" ),
  424. change.OldRefDesString,
  425. change.NewRefDes,
  426. ActionMessage[change.Action],
  427. UpdateRefDes != change.Action ? _( " will be ignored" )
  428. : wxString( wxT( "" ) ) );
  429. }
  430. ShowReport( message, RPT_SEVERITY_INFO );
  431. }
  432. void DIALOG_BOARD_REANNOTATE::LogFootprints( const wxString& aMessage,
  433. const std::vector<RefDesInfo>& aFootprints )
  434. {
  435. wxString message = aMessage;
  436. if( aFootprints.empty() )
  437. message += _( "\nNo footprints" );
  438. else
  439. {
  440. int i = 1;
  441. bool fpLocations = m_locationChoice->GetSelection() == 0;
  442. message += wxString::Format( _( "\n*********** Sort on %s ***********" ),
  443. fpLocations ? _( "Footprint Coordinates" )
  444. : _( "Reference Designator Coordinates" ) );
  445. message += wxString::Format( _( "\nSort Code %d" ), m_sortCode );
  446. for( const RefDesInfo& mod : aFootprints )
  447. {
  448. message += wxString::Format( _( "\n%d %s UUID: [%s], X, Y: %s, Rounded X, Y, %s" ),
  449. i++,
  450. mod.RefDesString,
  451. mod.Uuid.AsString(),
  452. CoordTowxString( mod.x, mod.y ),
  453. CoordTowxString( mod.roundedx, mod.roundedy ) );
  454. }
  455. }
  456. ShowReport( message, RPT_SEVERITY_INFO );
  457. }
  458. bool DIALOG_BOARD_REANNOTATE::ReannotateBoard()
  459. {
  460. std::vector<RefDesInfo> BadRefDes;
  461. wxString message, badrefdes;
  462. STRING_FORMATTER stringformatter;
  463. RefDesChange* newref;
  464. NETLIST netlist;
  465. if( !BuildFootprintList( BadRefDes ) )
  466. {
  467. ShowReport( _( "Selected options resulted in errors! Change them and try again." ),
  468. RPT_SEVERITY_ERROR );
  469. return false;
  470. }
  471. if( !BadRefDes.empty() )
  472. {
  473. message.Printf( _( "\nPCB has %d empty or invalid reference designations."
  474. "\nRecommend running DRC with 'Test for parity between PCB and "
  475. "schematic' checked.\n" ),
  476. (int) BadRefDes.size() );
  477. for( const RefDesInfo& mod : BadRefDes )
  478. {
  479. badrefdes += wxString::Format( _( "\nRefDes: %s Footprint: %s:%s at %s on PCB." ),
  480. mod.RefDesString,
  481. mod.FPID.GetLibNickname().wx_str(),
  482. mod.FPID.GetLibItemName().wx_str(),
  483. CoordTowxString( mod.x, mod.y ) );
  484. }
  485. ShowReport( message + badrefdes + wxT( "\n" ), RPT_SEVERITY_WARNING );
  486. message += _( "Reannotate anyway?" );
  487. if( !IsOK( m_frame, message ) )
  488. return false;
  489. }
  490. BOARD_COMMIT commit( m_frame );
  491. for( FOOTPRINT* footprint : m_footprints )
  492. {
  493. newref = GetNewRefDes( footprint );
  494. if( nullptr == newref )
  495. return false;
  496. commit.Modify( footprint ); // Make a copy for undo
  497. footprint->SetReference( newref->NewRefDes ); // Update the PCB reference
  498. m_frame->GetCanvas()->GetView()->Update( footprint ); // Touch the footprint
  499. }
  500. commit.Push( wxT( "Geographic reannotation" ) );
  501. return true;
  502. }
  503. bool DIALOG_BOARD_REANNOTATE::BuildFootprintList( std::vector<RefDesInfo>& aBadRefDes )
  504. {
  505. bool annotateSelected = m_AnnotateSelection->GetValue();
  506. bool annotateFront = m_AnnotateFront->GetValue(); // Unless only doing back
  507. bool annotateBack = m_AnnotateBack->GetValue(); // Unless only doing front
  508. bool skipLocked = m_ExcludeLocked->GetValue();
  509. int errorcount = 0;
  510. size_t firstnum = 0;
  511. m_frontFootprints.clear();
  512. m_backFootprints.clear();
  513. m_excludeArray.clear();
  514. m_footprints = m_frame->GetBoard()->Footprints();
  515. std::vector<KIID> selected;
  516. if( annotateSelected )
  517. {
  518. for( EDA_ITEM* item : m_selection )
  519. {
  520. // Get the timestamps of selected footprints
  521. if( item->Type() == PCB_FOOTPRINT_T )
  522. selected.push_back( item->m_Uuid );
  523. }
  524. }
  525. wxString exclude;
  526. // Break exclude list into words.
  527. for( auto thischar : m_ExcludeList->GetValue() )
  528. {
  529. if( ( ' ' == thischar ) || ( ',' == thischar ) )
  530. {
  531. m_excludeArray.push_back( exclude );
  532. exclude.clear();
  533. }
  534. else
  535. exclude += thischar;
  536. if( !exclude.empty() )
  537. m_excludeArray.push_back( exclude );
  538. }
  539. RefDesInfo fpData;
  540. bool useModuleLocation = m_locationChoice->GetSelection() == 0;
  541. for( FOOTPRINT* footprint : m_footprints )
  542. {
  543. fpData.Uuid = footprint->m_Uuid;
  544. fpData.RefDesString = footprint->GetReference();
  545. fpData.FPID = footprint->GetFPID();
  546. fpData.x = useModuleLocation ? footprint->GetPosition().x
  547. : footprint->Reference().GetPosition().x;
  548. fpData.y = useModuleLocation ? footprint->GetPosition().y
  549. : footprint->Reference().GetPosition().y;
  550. fpData.roundedx = RoundToGrid( fpData.x, m_sortGridx ); // Round to sort
  551. fpData.roundedy = RoundToGrid( fpData.y, m_sortGridy );
  552. fpData.Front = footprint->GetLayer() == F_Cu;
  553. fpData.Action = UpdateRefDes; // Usually good
  554. if( fpData.RefDesString.IsEmpty() )
  555. {
  556. fpData.Action = EmptyRefDes;
  557. }
  558. else
  559. {
  560. firstnum = fpData.RefDesString.find_first_of( wxT( "0123456789" ) );
  561. if( std::string::npos == firstnum )
  562. fpData.Action = InvalidRefDes; // do not change ref des such as 12 or +1, or L
  563. }
  564. // Get the type (R, C, etc)
  565. fpData.RefDesType = fpData.RefDesString.substr( 0, firstnum );
  566. for( wxString excluded : m_excludeArray )
  567. {
  568. if( excluded == fpData.RefDesType ) // Am I supposed to exclude this type?
  569. {
  570. fpData.Action = Exclude; // Yes
  571. break;
  572. }
  573. }
  574. if(( fpData.Front && annotateBack ) || // If a front fp and doing backs only
  575. ( !fpData.Front && annotateFront ) || // If a back fp and doing front only
  576. ( footprint->IsLocked() && skipLocked ) ) // If excluding locked and it is locked
  577. {
  578. fpData.Action = Exclude;
  579. }
  580. if( annotateSelected )
  581. { // If only annotating selected c
  582. fpData.Action = Exclude; // Assume it isn't selected
  583. for( KIID sel : selected )
  584. {
  585. if( fpData.Uuid == sel )
  586. { // Found in selected footprints
  587. fpData.Action = UpdateRefDes; // Update it
  588. break;
  589. }
  590. }
  591. }
  592. if( fpData.Front )
  593. m_frontFootprints.push_back( fpData );
  594. else
  595. m_backFootprints.push_back( fpData );
  596. }
  597. // Determine the sort order for the front.
  598. SetSortCodes( FrontDirectionsArray, m_sortCode );
  599. // Sort the front footprints.
  600. sort( m_frontFootprints.begin(), m_frontFootprints.end(), ModuleCompare );
  601. // Determine the sort order for the back.
  602. SetSortCodes( BackDirectionsArray, m_sortCode );
  603. // Sort the back footprints.
  604. sort( m_backFootprints.begin(), m_backFootprints.end(), ModuleCompare );
  605. m_refDesTypes.clear();
  606. m_changeArray.clear();
  607. BuildUnavailableRefsList();
  608. if( !m_frontFootprints.empty() )
  609. {
  610. BuildChangeArray( m_frontFootprints, wxAtoi( m_FrontRefDesStart->GetValue() ),
  611. m_FrontPrefix->GetValue(), m_RemoveFrontPrefix->GetValue(), aBadRefDes );
  612. }
  613. if( !m_backFootprints.empty() )
  614. {
  615. BuildChangeArray( m_backFootprints, wxAtoi( m_BackRefDesStart->GetValue() ),
  616. m_BackPrefix->GetValue(), m_RemoveBackPrefix->GetValue(), aBadRefDes );
  617. }
  618. if( !m_changeArray.empty() )
  619. sort( m_changeArray.begin(), m_changeArray.end(), ChangeArrayCompare );
  620. LogChangePlan();
  621. size_t changearraysize = m_changeArray.size();
  622. for( size_t i = 0; i < changearraysize; i++ ) // Scan through for duplicates if update or skip
  623. {
  624. if( ( m_changeArray[i].Action != EmptyRefDes )
  625. && ( m_changeArray[i].Action != InvalidRefDes ) )
  626. {
  627. for( size_t j = i + 1; j < changearraysize; j++ )
  628. {
  629. if( m_changeArray[i].NewRefDes == m_changeArray[j].NewRefDes )
  630. {
  631. ShowReport( wxString::Format( _( "Duplicate instances of %s" ),
  632. m_changeArray[j].NewRefDes ),
  633. RPT_SEVERITY_ERROR );
  634. if( errorcount++ > MAXERROR )
  635. {
  636. ShowReport( _( "Aborted: too many errors" ), RPT_SEVERITY_ERROR );
  637. break;
  638. }
  639. }
  640. }
  641. }
  642. if( errorcount > MAXERROR )
  643. break;
  644. }
  645. return ( 0 == errorcount );
  646. }
  647. void DIALOG_BOARD_REANNOTATE::BuildUnavailableRefsList()
  648. {
  649. std::vector<RefDesInfo> excludedFootprints;
  650. for( RefDesInfo fpData : m_frontFootprints )
  651. {
  652. if( fpData.Action == Exclude )
  653. excludedFootprints.push_back( fpData );
  654. }
  655. for( RefDesInfo fpData : m_backFootprints )
  656. {
  657. if( fpData.Action == Exclude )
  658. excludedFootprints.push_back( fpData );
  659. }
  660. for( RefDesInfo fpData : excludedFootprints )
  661. {
  662. if( fpData.Action == Exclude )
  663. {
  664. RefDesTypeStr* refDesInfo = GetOrBuildRefDesInfo( fpData.RefDesType );
  665. refDesInfo->UnavailableRefs.insert( UTIL::GetRefDesNumber( fpData.RefDesString ) );
  666. }
  667. }
  668. }
  669. void DIALOG_BOARD_REANNOTATE::BuildChangeArray( std::vector<RefDesInfo>& aFootprints,
  670. unsigned int aStartRefDes, const wxString& aPrefix,
  671. bool aRemovePrefix,
  672. std::vector<RefDesInfo>& aBadRefDes )
  673. {
  674. size_t prefixsize = aPrefix.size();
  675. bool haveprefix = ( 0 != prefixsize ); // Do I have a prefix?
  676. bool addprefix = haveprefix & !aRemovePrefix; // Yes- and I'm not removing it
  677. aRemovePrefix &= haveprefix; // Only remove if I have a prefix
  678. bool prefixpresent; // Prefix found
  679. wxString logstring = ( aFootprints.front().Front ) ? _( "\n\nFront Footprints" )
  680. : _( "\n\nBack Footprints" );
  681. LogFootprints( logstring, aFootprints );
  682. if( 0 != aStartRefDes ) // Initialize the change array if present
  683. {
  684. for( size_t i = 0; i < m_refDesTypes.size(); i++ )
  685. m_refDesTypes[i].LastUsedRefDes = aStartRefDes;
  686. }
  687. for( RefDesInfo fpData : aFootprints )
  688. {
  689. RefDesChange change;
  690. change.Uuid = fpData.Uuid;
  691. change.Action = fpData.Action;
  692. change.OldRefDesString = fpData.RefDesString;
  693. change.NewRefDes = fpData.RefDesString;
  694. change.Front = fpData.Front;
  695. if( fpData.RefDesString.IsEmpty() )
  696. fpData.Action = EmptyRefDes;
  697. if( ( change.Action == EmptyRefDes ) || ( change.Action == InvalidRefDes ) )
  698. {
  699. m_changeArray.push_back( change );
  700. aBadRefDes.push_back( fpData );
  701. continue;
  702. }
  703. if( change.Action == UpdateRefDes )
  704. {
  705. prefixpresent = ( 0 == fpData.RefDesType.find( aPrefix ) );
  706. if( addprefix && !prefixpresent )
  707. fpData.RefDesType.insert( 0, aPrefix ); // Add prefix once only
  708. if( aRemovePrefix && prefixpresent ) // If there is a prefix remove it
  709. fpData.RefDesType.erase( 0, prefixsize );
  710. RefDesTypeStr* refDesInfo = GetOrBuildRefDesInfo( fpData.RefDesType, aStartRefDes );
  711. unsigned int newRefDesNumber = refDesInfo->LastUsedRefDes + 1;
  712. while( refDesInfo->UnavailableRefs.count( newRefDesNumber ) )
  713. newRefDesNumber++;
  714. change.NewRefDes = refDesInfo->RefDesType + std::to_string( newRefDesNumber );
  715. refDesInfo->LastUsedRefDes = newRefDesNumber;
  716. }
  717. m_changeArray.push_back( change );
  718. }
  719. }
  720. RefDesChange* DIALOG_BOARD_REANNOTATE::GetNewRefDes( FOOTPRINT* aFootprint )
  721. {
  722. size_t i;
  723. for( i = 0; i < m_changeArray.size(); i++ )
  724. {
  725. if( aFootprint->m_Uuid == m_changeArray[i].Uuid )
  726. return ( &m_changeArray[i] );
  727. }
  728. ShowReport( _( "Footprint not found in changelist" ) + wxS( " " ) + aFootprint->GetReference(),
  729. RPT_SEVERITY_ERROR );
  730. return nullptr; // Should never happen
  731. }