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-2021 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 
26 #include <algorithm>
27 #include <base_units.h>
28 #include <bitmaps.h>
29 #include <board_commit.h>
30 #include <confirm.h>
31 #include <ctype.h>
32 #include <dialog_board_reannotate.h>
33 #include <fstream>
34 #include <string_utils.h>  // StrNumCmp
35 #include <kiface_base.h>
36 #include <mail_type.h>
37 #include <pcbnew_settings.h>
38 #include <refdes_utils.h>
39 #include <sstream>
40 #include <tool/tool_manager.h>
41 #include <tool/grid_menu.h>
42 #include <wx/valtext.h>
43 
44 
45 bool SortYFirst;
46 bool DescendingFirst;
47 bool DescendingSecond;
48 
49 //
50 // This converts the index into a sort code. Note that Back sort code will have left and
51 // right swapped.
52 //
53 int FrontDirectionsArray[] = {
54     SORTYFIRST + ASCENDINGFIRST + ASCENDINGSECOND,   // "Top to bottom, left to right",  //  100
55     SORTYFIRST + ASCENDINGFIRST + DESCENDINGSECOND,  // "Top to bottom, right to left",  //  101
56     SORTYFIRST + DESCENDINGFIRST + ASCENDINGSECOND,  // "Back to Front, left to right",  //  110
57     SORTYFIRST + DESCENDINGFIRST + DESCENDINGSECOND, // "Back to Front, right to left",  //  111
58     SORTXFIRST + ASCENDINGFIRST + ASCENDINGSECOND,   // "Left to right, Front to Back",  //  000
59     SORTXFIRST + ASCENDINGFIRST + DESCENDINGSECOND,  // "Left to right, Back to Front",  //  001
60     SORTXFIRST + DESCENDINGFIRST + ASCENDINGSECOND,  // "Right to left, Front to Back",  //  010
61     SORTXFIRST + DESCENDINGFIRST + DESCENDINGSECOND  // "Right to left, Back to Front",  //  011
62 };
63 
64 
65 //
66 // Back Left/Right is opposite because it is a mirror image (coordinates are from the top)
67 //
68 int BackDirectionsArray[] = {
69     SORTYFIRST + ASCENDINGFIRST + DESCENDINGSECOND,  // "Top to bottom, left to right",  //  101
70     SORTYFIRST + ASCENDINGFIRST + ASCENDINGSECOND,   // "Top to bottom, right to left",  //  100
71     SORTYFIRST + DESCENDINGFIRST + DESCENDINGSECOND, // "Bottom to top, left to right",  //  111
72     SORTYFIRST + DESCENDINGFIRST + ASCENDINGSECOND,  // "Bottom to top, right to left",  //  110
73     SORTXFIRST + DESCENDINGFIRST + ASCENDINGSECOND,  // "Left to right, top to bottom",  //  010
74     SORTXFIRST + DESCENDINGFIRST + DESCENDINGSECOND, // "Left to right, bottom to top",  //  011
75     SORTXFIRST + ASCENDINGFIRST + ASCENDINGSECOND,   // "Right to left, top to bottom",  //  000
76     SORTXFIRST + ASCENDINGFIRST + DESCENDINGSECOND   // "Right to left, bottom to top",  //  001
77 };
78 
79 #define SetSortCodes( DirArray, Code )                                     \
80     {                                                                      \
81         SortYFirst       = ( ( DirArray[Code] & SORTYFIRST ) != 0 );       \
82         DescendingFirst  = ( ( DirArray[Code] & DESCENDINGFIRST ) != 0 );  \
83         DescendingSecond = ( ( DirArray[Code] & DESCENDINGSECOND ) != 0 ); \
84     }
85 
86 
87 wxString AnnotateString[] = {
88     _( "All" ),          // AnnotateAll
89     _( "Only front" ),   // AnnotateFront
90     _( "Only back" ),    // AnnotateBack
91     _( "Only selected" ) // AnnotateSelected
92 };
93 
94 
95 wxString ActionMessage[] = {
96     "",             // UpdateRefDes
97     _( "Empty" ),   // EmptyRefDes
98     _( "Invalid" ), // InvalidRefDes
99     _( "Excluded" ) // Exclude
100 };
101 
102 
DIALOG_BOARD_REANNOTATE(PCB_EDIT_FRAME * aParentFrame)103 DIALOG_BOARD_REANNOTATE::DIALOG_BOARD_REANNOTATE( PCB_EDIT_FRAME* aParentFrame )
104         : DIALOG_BOARD_REANNOTATE_BASE( aParentFrame ),
105           m_footprints( aParentFrame->GetBoard()->Footprints() )
106 {
107     m_frame  = aParentFrame;
108     m_Config = Kiface().KifaceSettings();
109     InitValues();
110 
111     m_FrontRefDesStart->SetValidator( wxTextValidator( wxFILTER_DIGITS ) );
112     m_BackRefDesStart->SetValidator( wxTextValidator( wxFILTER_DIGITS ) );
113 
114     m_sdbSizerOK->SetLabel( _( "Reannotate PCB" ) );
115     m_sdbSizerCancel->SetLabel( _( "Close" ) );
116     m_sdbSizer->Layout();
117 
118     m_settings = aParentFrame->config();
119     wxArrayString gridslist;
120     GRID_MENU::BuildChoiceList( &gridslist, m_settings, aParentFrame );
121 
122     if( -1 == m_gridIndex ) // If no default loaded
123         m_gridIndex = m_settings->m_Window.grid.last_size_idx;        // Get the current grid size
124 
125     m_sortGridx = m_frame->GetCanvas()->GetGAL()->GetGridSize().x;
126     m_sortGridy = m_frame->GetCanvas()->GetGAL()->GetGridSize().y;
127 
128     m_GridChoice->Set( gridslist );
129     m_GridChoice->SetSelection( m_gridIndex );
130 
131     for( wxRadioButton* button : m_sortButtons )
132         button->SetValue( false );
133 
134     m_sortButtons[m_sortCode]->SetValue( true );
135 
136     m_selection = m_frame->GetToolManager()->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
137 
138     if( !m_selection.Empty() )
139         m_annotationChoice = AnnotationChoice::AnnotateSelected;
140 
141     for( wxRadioButton* button : AnnotateWhat )
142         button->SetValue( false );
143 
144     m_annotationChoice = ( m_sortCode >= (int) AnnotateWhat.size() ) ?
145                          AnnotationChoice::AnnotateAll :
146                          m_annotationChoice;
147 
148     AnnotateWhat[m_annotationChoice]->SetValue( true );
149 
150     reannotate_down_right_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_right_down ) );
151     reannotate_right_down_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_left_down ) );
152     reannotate_down_left_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_right_up ) );
153     reannotate_left_down_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_left_up ) );
154     reannotate_up_right_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_down_left ) );
155     reannotate_right_up_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_up_left ) );
156     reannotate_up_left_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_down_right ) );
157     reannotate_left_up_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_up_right ) );
158 
159     m_ExcludeList->SetToolTip( m_ExcludeListText->GetToolTipText() );
160     m_GridChoice->SetToolTip( m_SortGridText->GetToolTipText() );
161 
162     m_MessageWindow->SetFileName( Prj().GetProjectPath() + wxT( "report.txt" ) );
163 
164     finishDialogSettings();
165 }
166 
167 
~DIALOG_BOARD_REANNOTATE()168 DIALOG_BOARD_REANNOTATE::~DIALOG_BOARD_REANNOTATE()
169 {
170     GetParameters(); // Get the current menu settings
171     PCBNEW_SETTINGS* cfg = m_frame->GetPcbNewSettings();
172     cfg->m_Reannotate.sort_on_fp_location = m_locationChoice->GetSelection() == 0;
173     cfg->m_Reannotate.remove_front_prefix     = m_RemoveFrontPrefix->GetValue();
174     cfg->m_Reannotate.remove_back_prefix      = m_RemoveBackPrefix->GetValue();
175     cfg->m_Reannotate.exclude_locked          = m_ExcludeLocked->GetValue();
176 
177     cfg->m_Reannotate.grid_index              = m_gridIndex;
178     cfg->m_Reannotate.sort_code               = m_sortCode;
179     cfg->m_Reannotate.annotation_choice       = m_annotationChoice;
180     cfg->m_Reannotate.report_severity         = m_severity;
181 
182     cfg->m_Reannotate.front_refdes_start      = m_FrontRefDesStart->GetValue();
183     cfg->m_Reannotate.back_refdes_start       = m_BackRefDesStart->GetValue();
184     cfg->m_Reannotate.front_prefix            = m_FrontPrefix->GetValue();
185     cfg->m_Reannotate.back_prefix             = m_BackPrefix->GetValue();
186     cfg->m_Reannotate.exclude_list            = m_ExcludeList->GetValue();
187     cfg->m_Reannotate.report_file_name        = m_MessageWindow->GetFileName();
188 }
189 
190 
InitValues(void)191 void DIALOG_BOARD_REANNOTATE::InitValues( void )
192 {
193     PCBNEW_SETTINGS* cfg = m_frame->GetPcbNewSettings();
194     m_locationChoice->SetSelection( cfg->m_Reannotate.sort_on_fp_location ? 0 : 1 );
195     m_RemoveFrontPrefix->SetValue( cfg->m_Reannotate.remove_front_prefix );
196     m_RemoveBackPrefix->SetValue( cfg->m_Reannotate.remove_back_prefix );
197     m_ExcludeLocked->SetValue( cfg->m_Reannotate.exclude_locked );
198 
199     m_gridIndex         = cfg->m_Reannotate.grid_index ;
200     m_sortCode          = cfg->m_Reannotate.sort_code ;
201     m_annotationChoice  = cfg->m_Reannotate.annotation_choice ;
202     m_severity          = cfg->m_Reannotate.report_severity;
203 
204     m_FrontRefDesStart->SetValue( cfg->m_Reannotate.front_refdes_start );
205     m_BackRefDesStart->SetValue( cfg->m_Reannotate.back_refdes_start );
206     m_FrontPrefix->SetValue( cfg->m_Reannotate.front_prefix );
207     m_BackPrefix->SetValue( cfg->m_Reannotate.back_prefix );
208     m_ExcludeList->SetValue( cfg->m_Reannotate.exclude_list );
209     m_MessageWindow->SetFileName( cfg->m_Reannotate.report_file_name );
210 }
211 
212 
OnCloseClick(wxCommandEvent & event)213 void DIALOG_BOARD_REANNOTATE::OnCloseClick( wxCommandEvent& event )
214 {
215     EndDialog( wxID_OK );
216 }
217 
218 
FilterPrefix(wxTextCtrl * aPrefix)219 void DIALOG_BOARD_REANNOTATE::FilterPrefix( wxTextCtrl* aPrefix )
220 {
221     std::string tmps = VALIDPREFIX;
222 
223     if( aPrefix->GetValue().empty() )
224         return; //Should never happen
225 
226     char lastc = aPrefix->GetValue().Last();
227 
228     if( isalnum( (int) lastc ) )
229         return;
230 
231     if( std::string::npos != tmps.find( lastc ) )
232         return;
233 
234     tmps = aPrefix->GetValue();
235     aPrefix->Clear();
236     tmps.pop_back();
237     aPrefix->AppendText( tmps );
238 }
239 
240 
GetOrBuildRefDesInfo(const wxString & aRefDesPrefix,unsigned int aStartRefDes)241 RefDesTypeStr* DIALOG_BOARD_REANNOTATE::GetOrBuildRefDesInfo( const wxString& aRefDesPrefix,
242                                                               unsigned int    aStartRefDes )
243 {
244     unsigned int requiredLastRef = ( aStartRefDes == 0 ? 1 : aStartRefDes ) - 1;
245 
246     for( size_t i = 0; i < m_refDesTypes.size(); i++ ) // See if it is in the types array
247     {
248         if( m_refDesTypes[i].RefDesType == aRefDesPrefix ) // Found it!
249         {
250             m_refDesTypes[i].LastUsedRefDes = std::max( m_refDesTypes[i].LastUsedRefDes,
251                                                         requiredLastRef );
252 
253             return &m_refDesTypes[i];
254         }
255     }
256 
257     // Wasn't in the types array so add it
258     RefDesTypeStr newtype;
259     newtype.RefDesType = aRefDesPrefix;
260     newtype.LastUsedRefDes = requiredLastRef;
261     m_refDesTypes.push_back( newtype );
262 
263     return &m_refDesTypes.back();
264 }
265 
266 
FilterFrontPrefix(wxCommandEvent & event)267 void DIALOG_BOARD_REANNOTATE::FilterFrontPrefix( wxCommandEvent& event )
268 {
269     FilterPrefix( m_FrontPrefix );
270 }
271 
272 
FilterBackPrefix(wxCommandEvent & event)273 void DIALOG_BOARD_REANNOTATE::FilterBackPrefix( wxCommandEvent& event )
274 {
275     FilterPrefix( m_BackPrefix );
276 }
277 
278 
OnApplyClick(wxCommandEvent & event)279 void DIALOG_BOARD_REANNOTATE::OnApplyClick( wxCommandEvent& event )
280 {
281     wxString warning;
282 
283     if( m_frame->GetBoard()->IsEmpty() )
284     {
285         ShowReport( _( "No PCB to reannotate!" ), RPT_SEVERITY_ERROR );
286         return;
287     }
288 
289     GetParameters(); // Figure out how this is to be done
290     MakeSampleText( warning );
291 
292     if( !IsOK( m_frame, warning ) )
293         return;
294 
295     if( ReannotateBoard() )
296     {
297         ShowReport( _( "PCB successfully reannotated" ), RPT_SEVERITY_ACTION );
298         ShowReport( _( "PCB annotation changes should be synchronized with schematic using "
299                        "the \"Update Schematic from PCB\" tool." ), RPT_SEVERITY_WARNING );
300     }
301 
302     m_MessageWindow->SetLazyUpdate( false );
303     m_MessageWindow->Flush( false );
304     m_frame->GetCanvas()->Refresh(); // Redraw
305     m_frame->OnModify();             // Need to save file on exit.
306 }
307 
308 
MakeSampleText(wxString & aMessage)309 void DIALOG_BOARD_REANNOTATE::MakeSampleText( wxString& aMessage )
310 {
311     wxString tmp;
312 
313     aMessage.Printf( _( "\n%s footprints will be reannotated." ),
314                      _( AnnotateString[m_annotationChoice] ) );
315 
316     if( !m_ExcludeList->GetValue().empty() )
317     {
318         aMessage += wxString::Format( _( "\nAny reference types %s will not be annotated." ),
319                                       m_ExcludeList->GetValue() );
320     }
321 
322     if( m_ExcludeLocked->GetValue() )
323         aMessage += wxString::Format( _( "\nLocked footprints will not be annotated" ) );
324 
325     if( !m_AnnotateBack->GetValue() )
326     {
327         aMessage += wxString::Format( _( "\nFront footprints will start at %s" ),
328                                       m_FrontRefDesStart->GetValue() );
329     }
330 
331     if( !m_AnnotateFront->GetValue() )
332     {
333         bool frontPlusOne = ( 0 == wxAtoi( m_BackRefDesStart->GetValue() ) )
334                             && !m_AnnotateBack->GetValue();
335 
336         aMessage += wxString::Format( _( "\nBack footprints will start at %s." ),
337                                       frontPlusOne ? _( "the last front footprint + 1" ) :
338                                       m_BackRefDesStart->GetValue() );
339     }
340 
341     if( !m_FrontPrefix->GetValue().empty() )
342     {
343         if( m_RemoveFrontPrefix->GetValue() )
344         {
345             aMessage += wxString::Format( _( "\nFront footprints starting with '%s' will have "
346                                              "the prefix removed." ),
347                                           m_FrontPrefix->GetValue() );
348         }
349         else
350         {
351             aMessage += wxString::Format( _( "\nFront footprints will have '%s' inserted as a "
352                                              "prefix." ),
353                                           m_FrontPrefix->GetValue() );
354         }
355     }
356 
357     if( !m_BackPrefix->GetValue().empty() )
358     {
359         if( m_RemoveBackPrefix->GetValue() )
360         {
361             aMessage += wxString::Format( _( "\nBack footprints starting with '%s' will have the "
362                                              "prefix removed." ),
363                                           m_BackPrefix->GetValue() );
364         }
365         else
366         {
367             aMessage += wxString::Format( _( "\nBack footprints will have '%s' inserted as a "
368                                              "prefix." ),
369                                           m_BackPrefix->GetValue() );
370         }
371     }
372 
373     bool fpLocation = m_locationChoice->GetSelection() == 0;
374 
375     aMessage += wxString::Format( _( "\nPrior to sorting by %s, the coordinates of which will be "
376                                      "rounded to a %s, %s grid." ),
377                                   fpLocation ? _( "footprint location" )
378                                              : _( "reference designator location" ),
379                                   MessageTextFromValue( m_units, m_sortGridx ),
380                                   MessageTextFromValue( m_units, m_sortGridy ) );
381 
382     ShowReport( aMessage, RPT_SEVERITY_INFO );
383 }
384 
385 
GetParameters()386 void DIALOG_BOARD_REANNOTATE::GetParameters()
387 {
388     m_sortCode = 0; // Convert radio button to sort direction code
389 
390     for( wxRadioButton* sortbuttons : m_sortButtons )
391     {
392         if( sortbuttons->GetValue() )
393             break;
394 
395         m_sortCode++;
396     }
397 
398     if( m_sortCode >= (int) m_sortButtons.size() )
399         m_sortCode = 0;
400 
401     m_frontPrefixString = m_FrontPrefix->GetValue();
402     m_backPrefixString  = m_BackPrefix->GetValue();
403 
404     // Get the chosen sort grid for rounding
405     m_gridIndex = m_GridChoice->GetSelection();
406 
407     if( m_gridIndex >= ( int ) m_settings->m_Window.grid.sizes.size() )
408     {
409         m_sortGridx = DoubleValueFromString( EDA_UNITS::MILS,
410                                              m_settings->m_Window.grid.user_grid_x );
411         m_sortGridy = DoubleValueFromString( EDA_UNITS::MILS,
412                                              m_settings->m_Window.grid.user_grid_y );
413     }
414     else
415     {
416         m_sortGridx = DoubleValueFromString( EDA_UNITS::MILS,
417                                              m_settings->m_Window.grid.sizes[ m_gridIndex ] );
418         m_sortGridy = m_sortGridx;
419     }
420 
421     int i = 0;
422 
423     for( wxRadioButton* button : AnnotateWhat )
424     {
425         if( button->GetValue() )
426             break;
427         else
428             i++;
429     }
430 
431     m_annotationChoice = ( i >= (int) AnnotateWhat.size() ) ? AnnotationChoice::AnnotateAll : i;
432 
433     m_MessageWindow->SetLazyUpdate( true );
434 }
435 
436 
RoundToGrid(int aCoord,int aGrid)437 int DIALOG_BOARD_REANNOTATE::RoundToGrid( int aCoord, int aGrid )
438 {
439     if( 0 == aGrid )
440         aGrid = MINGRID;
441 
442     int rounder;
443     rounder = aCoord % aGrid;
444     aCoord -= rounder;
445 
446     if( abs( rounder ) > ( aGrid / 2 ) )
447         aCoord += ( aCoord < 0 ? -aGrid : aGrid );
448 
449     return ( aCoord );
450 }
451 
452 
453 /// Compare function used to compare ChangeArray element for sort
454 /// @return true is A < B
ChangeArrayCompare(const RefDesChange & aA,const RefDesChange & aB)455 static bool ChangeArrayCompare( const RefDesChange& aA, const RefDesChange& aB )
456 {
457     return ( StrNumCmp( aA.OldRefDesString, aB.OldRefDesString ) < 0 );
458 }
459 
460 
461 /// Compare function to sort footprints.
462 /// @return true if the first coordinate should be before the second coordinate
ModuleCompare(const RefDesInfo & aA,const RefDesInfo & aB)463 static bool ModuleCompare( const RefDesInfo& aA, const RefDesInfo& aB )
464 {
465     int X0 = aA.roundedx, X1 = aB.roundedx, Y0 = aA.roundedy, Y1 = aB.roundedy;
466 
467     if( SortYFirst ) //If sorting by Y then X, swap X and Y
468     {
469         std::swap( X0, Y0 );
470         std::swap( X1, Y1 );
471     }
472 
473     // If descending, same compare just swap directions
474     if( DescendingFirst )
475         std::swap( X0, X1 );
476 
477     if( DescendingSecond )
478         std::swap( Y0, Y1 );
479 
480     if( X0 < X1 )
481         return ( true );  // yes, its smaller
482 
483     if( X0 > X1 )
484         return ( false ); // No its not
485 
486     if( Y0 < Y1 )
487         return ( true );  // same but equal
488 
489     return ( false );
490 }
491 
492 
CoordTowxString(int aX,int aY)493 wxString DIALOG_BOARD_REANNOTATE::CoordTowxString( int aX, int aY )
494 {
495     return wxString::Format( "%s, %s",
496                              MessageTextFromValue( m_units, aX ),
497                              MessageTextFromValue( m_units, aY ) );
498 }
499 
500 
ShowReport(const wxString & aMessage,SEVERITY aSeverity)501 void DIALOG_BOARD_REANNOTATE::ShowReport( const wxString& aMessage, SEVERITY aSeverity )
502 {
503     size_t pos = 0, prev = 0;
504 
505     do
506     {
507         pos = aMessage.ToStdString().find( '\n', prev );
508         m_MessageWindow->Report( aMessage.ToStdString().substr( prev, pos - prev ), aSeverity );
509         prev = pos + 1;
510     } while( std::string::npos != pos );
511 
512 }
513 
514 
LogChangePlan()515 void DIALOG_BOARD_REANNOTATE::LogChangePlan()
516 {
517     int      i = 1;
518     wxString message;
519 
520     message.Printf( _( "\n\nThere are %i types of reference designations\n"
521                        "**********************************************************\n" ),
522                     (int) m_refDesTypes.size() );
523 
524     for( RefDesTypeStr Type : m_refDesTypes ) // Show all the types of refdes
525         message += Type.RefDesType + ( 0 == ( i++ % 16 ) ? "\n" : " " );
526 
527     if( !m_excludeArray.empty() )
528     {
529         wxString excludes;
530 
531         for( wxString& exclude : m_excludeArray ) // Show the refdes we are excluding
532             excludes += exclude + " ";
533 
534         message += wxString::Format( _( "\nExcluding: %s from reannotation\n\n" ), excludes );
535     }
536 
537     message += _( "\n    Change Array\n***********************\n" );
538 
539     for( const RefDesChange& change : m_changeArray )
540     {
541         message += wxString::Format(
542                 "%s -> %s  %s %s\n", change.OldRefDesString, change.NewRefDes,
543                 ActionMessage[change.Action],
544                 UpdateRefDes != change.Action ? _( " will be ignored" ) : wxT( "" ) );
545     }
546 
547     ShowReport( message, RPT_SEVERITY_INFO );
548 }
549 
550 
LogFootprints(const wxString & aMessage,const std::vector<RefDesInfo> & aFootprints)551 void DIALOG_BOARD_REANNOTATE::LogFootprints( const wxString& aMessage,
552                                              const std::vector<RefDesInfo>& aFootprints )
553 {
554     wxString message = aMessage;
555 
556     if( aFootprints.empty() )
557         message += _( "\nNo footprints" );
558     else
559     {
560         int i = 1;
561         bool fpLocations = m_locationChoice->GetSelection() == 0;
562 
563         message += wxString::Format( _( "\n*********** Sort on %s ***********" ),
564                                      fpLocations ? _( "Footprint Coordinates" )
565                                                  : _( "Reference Designator Coordinates" ) );
566 
567         message += wxString::Format( _( "\nSort Code %d" ), m_sortCode );
568 
569         for( const RefDesInfo& mod : aFootprints )
570         {
571             message += wxString::Format( _( "\n%d %s UUID: [%s], X, Y: %s, Rounded X, Y, %s" ),
572                                          i++,
573                                          mod.RefDesString,
574                                          mod.Uuid.AsString(),
575                                          CoordTowxString( mod.x, mod.y ),
576                                          CoordTowxString( mod.roundedx, mod.roundedy ) );
577         }
578     }
579 
580     ShowReport( message, RPT_SEVERITY_INFO );
581 }
582 
583 
ReannotateBoard()584 bool DIALOG_BOARD_REANNOTATE::ReannotateBoard()
585 {
586     std::vector<RefDesInfo> BadRefDes;
587     wxString                message, badrefdes;
588     STRING_FORMATTER        stringformatter;
589     RefDesChange*           newref;
590     NETLIST                 netlist;
591 
592     if( !BuildFootprintList( BadRefDes ) )
593     {
594         ShowReport( "Selected options resulted in errors! Change them and try again.",
595                     RPT_SEVERITY_ERROR );
596         return false;
597     }
598 
599     if( !BadRefDes.empty() )
600     {
601         message.Printf(
602                 _( "\nPCB has %d empty or invalid reference designations."
603                    "\nRecommend running DRC with 'Test for parity between PCB and schematic' checked.\n" ),
604                 (int) BadRefDes.size() );
605 
606         for( const RefDesInfo& mod : BadRefDes )
607         {
608             badrefdes += wxString::Format( _( "\nRefDes: %s Footprint: %s:%s at %s on PCB." ),
609                                            mod.RefDesString,
610                                            mod.FPID.GetLibNickname().wx_str(),
611                                            mod.FPID.GetLibItemName().wx_str(),
612                                            CoordTowxString( mod.x, mod.y ) );
613         }
614 
615         ShowReport( message + badrefdes + "\n", RPT_SEVERITY_WARNING );
616         message += _( "Reannotate anyway?" );
617 
618         if( !IsOK( m_frame, message ) )
619             return false;
620     }
621 
622     BOARD_COMMIT commit( m_frame );
623 
624     for( FOOTPRINT* footprint : m_footprints )
625     {
626         newref = GetNewRefDes( footprint );
627 
628         if( nullptr == newref )
629             return false;
630 
631         commit.Modify( footprint );                           // Make a copy for undo
632         footprint->SetReference( newref->NewRefDes );         // Update the PCB reference
633         m_frame->GetCanvas()->GetView()->Update( footprint ); // Touch the footprint
634     }
635 
636     commit.Push( "Geographic reannotation" );
637     return true;
638 }
639 
640 
BuildFootprintList(std::vector<RefDesInfo> & aBadRefDes)641 bool DIALOG_BOARD_REANNOTATE::BuildFootprintList( std::vector<RefDesInfo>& aBadRefDes )
642 {
643     bool annotateSelected;
644     bool annotateFront = m_AnnotateFront->GetValue(); // Unless only doing back
645     bool annotateBack  = m_AnnotateBack->GetValue();  // Unless only doing front
646     bool skipLocked    = m_ExcludeLocked->GetValue();
647 
648     int    errorcount = 0;
649     size_t firstnum   = 0;
650 
651     m_frontFootprints.clear();
652     m_backFootprints.clear();
653     m_excludeArray.clear();
654     m_footprints = m_frame->GetBoard()->Footprints();
655 
656     std::vector<KIID> selected;
657 
658     if( m_AnnotateSelection->GetValue() )
659     {
660         for( EDA_ITEM* item : m_selection )
661         {
662             // Get the timestamps of selected footprints
663             if( item->Type() == PCB_FOOTPRINT_T )
664                 selected.push_back( item->m_Uuid );
665         }
666     }
667 
668     annotateSelected = !selected.empty();
669 
670     wxString exclude;
671 
672     // Break exclude list into words.
673     for( auto thischar : m_ExcludeList->GetValue() )
674     {
675     	if( ( ' ' == thischar ) || ( ',' == thischar ) )
676         {
677             m_excludeArray.push_back( exclude );
678             exclude.clear();
679         }
680         else
681             exclude += thischar;
682 
683         if( !exclude.empty() )
684             m_excludeArray.push_back( exclude );
685     }
686 
687     RefDesInfo fpData;
688     bool       useModuleLocation = m_locationChoice->GetSelection() == 0;
689 
690     for( FOOTPRINT* footprint : m_footprints )
691     {
692         fpData.Uuid         = footprint->m_Uuid;
693         fpData.RefDesString = footprint->GetReference();
694         fpData.FPID         = footprint->GetFPID();
695         fpData.x            = useModuleLocation ? footprint->GetPosition().x
696                                                 : footprint->Reference().GetPosition().x;
697         fpData.y            = useModuleLocation ? footprint->GetPosition().y
698                                                 : footprint->Reference().GetPosition().y;
699         fpData.roundedx     = RoundToGrid( fpData.x, m_sortGridx ); // Round to sort
700         fpData.roundedy     = RoundToGrid( fpData.y, m_sortGridy );
701         fpData.Front        = footprint->GetLayer() == F_Cu;
702         fpData.Action       = UpdateRefDes; // Usually good
703 
704         if( fpData.RefDesString.IsEmpty() )
705         {
706             fpData.Action = EmptyRefDes;
707         }
708         else
709         {
710             firstnum = fpData.RefDesString.find_first_of( "0123456789" );
711 
712             if( std::string::npos == firstnum )
713                 fpData.Action = InvalidRefDes; // do not change ref des such as 12 or +1, or L
714         }
715 
716         // Get the type (R, C, etc)
717         fpData.RefDesType = fpData.RefDesString.substr( 0, firstnum );
718 
719         for( wxString excluded : m_excludeArray )
720         {
721             if( excluded == fpData.RefDesType ) // Am I supposed to exclude this type?
722             {
723                 fpData.Action = Exclude; // Yes
724                 break;
725             }
726         }
727 
728         if(( fpData.Front && annotateBack ) ||            // If a front fp and doing backs only
729                 ( !fpData.Front && annotateFront ) ||     // If a back fp and doing front only
730                 ( footprint->IsLocked() && skipLocked ) ) // If excluding locked and it is locked
731         {
732             fpData.Action = Exclude;
733         }
734 
735         if( annotateSelected )
736         {                                // If only annotating selected c
737             fpData.Action = Exclude;     // Assume it isn't selected
738 
739             for( KIID sel : selected )
740             {
741                 if( fpData.Uuid == sel )
742                 {                                  // Found in selected footprints
743                     fpData.Action = UpdateRefDes;  // Update it
744                     break;
745                 }
746             }
747         }
748 
749         if( fpData.Front )
750             m_frontFootprints.push_back( fpData );
751         else
752             m_backFootprints.push_back( fpData );
753     }
754 
755     // Determine the sort order for the front.
756     SetSortCodes( FrontDirectionsArray, m_sortCode );
757 
758     // Sort the front footprints.
759     sort( m_frontFootprints.begin(), m_frontFootprints.end(), ModuleCompare );
760 
761     // Determine the sort order for the back.
762     SetSortCodes( BackDirectionsArray, m_sortCode );
763 
764     // Sort the back footprints.
765     sort( m_backFootprints.begin(), m_backFootprints.end(), ModuleCompare );
766 
767     m_refDesTypes.clear();
768     m_changeArray.clear();
769 
770     BuildUnavailableRefsList();
771 
772     if( !m_frontFootprints.empty() )
773     {
774         BuildChangeArray( m_frontFootprints, wxAtoi( m_FrontRefDesStart->GetValue() ),
775                           m_FrontPrefix->GetValue(), m_RemoveFrontPrefix->GetValue(), aBadRefDes );
776     }
777 
778     if( !m_backFootprints.empty() )
779     {
780         BuildChangeArray( m_backFootprints, wxAtoi( m_BackRefDesStart->GetValue() ),
781                           m_BackPrefix->GetValue(), m_RemoveBackPrefix->GetValue(), aBadRefDes );
782     }
783 
784     if( !m_changeArray.empty() )
785         sort( m_changeArray.begin(), m_changeArray.end(), ChangeArrayCompare );
786 
787     LogChangePlan();
788 
789     size_t changearraysize = m_changeArray.size();
790 
791     for( size_t i = 0; i < changearraysize; i++ ) // Scan through for duplicates if update or skip
792     {
793         if( ( m_changeArray[i].Action != EmptyRefDes )
794                 && ( m_changeArray[i].Action != InvalidRefDes ) )
795         {
796             for( size_t j = i + 1; j < changearraysize; j++ )
797             {
798                 if( m_changeArray[i].NewRefDes == m_changeArray[j].NewRefDes )
799                 {
800                     ShowReport( "Duplicate instances of " + m_changeArray[j].NewRefDes,
801                                 RPT_SEVERITY_ERROR );
802 
803                     if( errorcount++ > MAXERROR )
804                     {
805                         ShowReport( _( "Aborted: too many errors" ), RPT_SEVERITY_ERROR );
806                         break;
807                     }
808                 }
809             }
810         }
811 
812         if( errorcount > MAXERROR )
813             break;
814     }
815 
816     return ( 0 == errorcount );
817 }
818 
BuildUnavailableRefsList()819 void DIALOG_BOARD_REANNOTATE::BuildUnavailableRefsList()
820 {
821     std::vector<RefDesInfo> excludedFootprints;
822 
823     for( RefDesInfo fpData : m_frontFootprints )
824     {
825         if( fpData.Action == Exclude )
826             excludedFootprints.push_back( fpData );
827     }
828 
829     for( RefDesInfo fpData : m_backFootprints )
830     {
831         if( fpData.Action == Exclude )
832             excludedFootprints.push_back( fpData );
833     }
834 
835     for( RefDesInfo fpData : excludedFootprints )
836     {
837         if( fpData.Action == Exclude )
838         {
839             RefDesTypeStr* refDesInfo = GetOrBuildRefDesInfo( fpData.RefDesType );
840             refDesInfo->UnavailableRefs.insert( UTIL::GetRefDesNumber( fpData.RefDesString ) );
841         }
842     }
843 }
844 
845 
BuildChangeArray(std::vector<RefDesInfo> & aFootprints,unsigned int aStartRefDes,const wxString & aPrefix,bool aRemovePrefix,std::vector<RefDesInfo> & aBadRefDes)846 void DIALOG_BOARD_REANNOTATE::BuildChangeArray( std::vector<RefDesInfo>& aFootprints,
847                                                 unsigned int aStartRefDes, const wxString& aPrefix,
848                                                 bool aRemovePrefix,
849                                                 std::vector<RefDesInfo>& aBadRefDes )
850 {
851     size_t   prefixsize = aPrefix.size();
852 
853     bool haveprefix = ( 0 != prefixsize );         // Do I have a prefix?
854     bool addprefix  = haveprefix & !aRemovePrefix; // Yes- and I'm not removing it
855     aRemovePrefix &= haveprefix;                   // Only remove if I have a prefix
856 
857     bool prefixpresent; // Prefix found
858 
859     wxString logstring = ( aFootprints.front().Front ) ? _( "\n\nFront Footprints" )
860                                                        : _( "\n\nBack Footprints" );
861     LogFootprints( logstring, aFootprints );
862 
863     if( 0 != aStartRefDes ) // Initialize the change array if present
864     {
865     	for( size_t i = 0; i < m_refDesTypes.size(); i++ )
866             m_refDesTypes[i].LastUsedRefDes = aStartRefDes;
867     }
868 
869 
870     for( RefDesInfo fpData : aFootprints )
871     {
872         RefDesChange change;
873 
874         change.Uuid            = fpData.Uuid;
875         change.Action          = fpData.Action;
876         change.OldRefDesString = fpData.RefDesString;
877         change.NewRefDes       = fpData.RefDesString;
878         change.Front           = fpData.Front;
879 
880         if( fpData.RefDesString.IsEmpty() )
881             fpData.Action = EmptyRefDes;
882 
883         if( ( change.Action == EmptyRefDes ) || ( change.Action == InvalidRefDes ) )
884         {
885             m_changeArray.push_back( change );
886             aBadRefDes.push_back( fpData );
887             continue;
888         }
889 
890         if( change.Action == UpdateRefDes )
891         {
892             prefixpresent = ( 0 == fpData.RefDesType.find( aPrefix ) );
893 
894             if( addprefix && !prefixpresent )
895                 fpData.RefDesType.insert( 0, aPrefix ); // Add prefix once only
896 
897             if( aRemovePrefix && prefixpresent ) // If there is a prefix remove it
898                 fpData.RefDesType.erase( 0, prefixsize );
899 
900             RefDesTypeStr* refDesInfo = GetOrBuildRefDesInfo( fpData.RefDesType, aStartRefDes );
901             unsigned int  newRefDesNumber = refDesInfo->LastUsedRefDes + 1;
902 
903             while( refDesInfo->UnavailableRefs.count( newRefDesNumber ) )
904                 newRefDesNumber++;
905 
906             change.NewRefDes = refDesInfo->RefDesType + std::to_string( newRefDesNumber );
907             refDesInfo->LastUsedRefDes = newRefDesNumber;
908         }
909 
910         m_changeArray.push_back( change );
911     }
912 }
913 
914 
GetNewRefDes(FOOTPRINT * aFootprint)915 RefDesChange* DIALOG_BOARD_REANNOTATE::GetNewRefDes( FOOTPRINT* aFootprint )
916 {
917     size_t i;
918 
919     for( i = 0; i < m_changeArray.size(); i++ )
920     {
921         if( aFootprint->m_Uuid == m_changeArray[i].Uuid )
922             return ( &m_changeArray[i] );
923     }
924 
925     ShowReport( _( "Footprint not found in changelist" ) + wxS( " " ) + aFootprint->GetReference(),
926                 RPT_SEVERITY_ERROR );
927 
928     return nullptr; // Should never happen
929 }
930