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