KiCad PCB EDA Suite
dialog_create_array.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, you may find one here:
18  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19  * or you may search the http://www.gnu.org website for the version 2 license,
20  * or you may write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22  */
23 
25 
26 #include <base_units.h>
27 #include <widgets/text_ctrl_eval.h>
28 #include <board.h>
29 #include <footprint.h>
30 #include <pcb_edit_frame.h>
31 #include <wx/msgdlg.h>
32 
33 #include <boost/algorithm/string/join.hpp>
34 
39 {
45  m_OptionsSet( true ),
46  m_GridNx( 5 ),
47  m_GridNy( 5 ),
48  m_GridDx( Millimeter2iu( 2.54 ) ),
49  m_GridDy( Millimeter2iu( 2.54 ) ),
50  m_GridOffsetX( 0 ),
51  m_GridOffsetY( 0 ),
52  m_GridStagger( 1 ),
53  m_GridStaggerType( 0 ), // rows
54  m_GridNumberingAxis( 0 ), // h then v
55  m_GridNumReverseAlt( false ),
56  m_GridNumStartSet( 1 ), // use specified start
57  m_Grid2dArrayNumbering( 0 ), // linear numbering
58  m_GridPrimaryAxisScheme( 0 ), // numeric
59  m_GridSecondaryAxisScheme( 0 ), // numeric
60  m_GridPrimaryNumOffset( "1" ), // numeric
61  m_GridSecondaryNumOffset( "1" ), // numeric
64  m_CircCentreX( 0 ),
65  m_CircCentreY( 0 ),
66  m_CircAngle( 0.0 ),
67  m_CircCount( 4 ),
68  m_CircNumStartSet( 1 ), // use specified start
70  m_CircNumberingOffset( "1" ),
72  m_CircRotatationStep( false ),
73  m_ArrayTypeTab( 0 ), // start on grid view
75  m_FootprintReannotate( true ) // Assign unique by default
76  {
77  }
78 
80 
81  long m_GridNx;
82  long m_GridNy;
83  long m_GridDx;
84  long m_GridDy;
88 
100 
113 };
114 
115 // Persistent options settings
117 
122 {
124  wxString m_label;
125 };
126 
131 static const std::vector<NUMBERING_LIST_DATA> numberingTypeData {
132  {
133  ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC,
134  _( "Numerals (0,1,2,...,9,10)" ),
135  },
136  {
137  ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_HEX,
138  _( "Hexadecimal (0,1,...,F,10,...)" ),
139  },
140  {
141  ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_ALPHA_NO_IOSQXZ,
142  _( "Alphabet, minus IOSQXZ" ),
143  },
144  {
145  ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_ALPHA_FULL,
146  _( "Alphabet, full 26 characters" ),
147  },
148 };
149 
151  std::unique_ptr<ARRAY_OPTIONS>& aSettings,
152  bool aIsFootprintEditor, const wxPoint& aOrigPos ) :
153  DIALOG_CREATE_ARRAY_BASE( aParent ),
154  m_settings( aSettings ),
155  m_originalItemPosition( aOrigPos ),
156  m_isFootprintEditor( aIsFootprintEditor ),
157  m_hSpacing( aParent, m_labelDx, m_entryDx, m_unitLabelDx ),
158  m_vSpacing( aParent, m_labelDy, m_entryDy, m_unitLabelDy ),
159  m_hOffset( aParent, m_labelOffsetX, m_entryOffsetX, m_unitLabelOffsetX ),
160  m_vOffset( aParent, m_labelOffsetY, m_entryOffsetY, m_unitLabelOffsetY ),
161  m_hCentre( aParent, m_labelCentreX, m_entryCentreX, m_unitLabelCentreX ),
162  m_vCentre( aParent, m_labelCentreY, m_entryCentreY, m_unitLabelCentreY ),
163  m_circRadius( aParent, m_labelCircRadius, m_valueCircRadius, m_unitLabelCircRadius ),
164  m_circAngle( aParent, m_labelCircAngle, m_entryCircAngle, m_unitLabelCircAngle ),
165  m_cfg_persister( s_arrayOptions.m_OptionsSet )
166 {
167  // Configure display origin transforms
174 
175  // Set up numbering scheme drop downs character set strings
176  for( const auto& numData : numberingTypeData )
177  {
178  const wxString label = wxGetTranslation( numData.m_label );
179  void* clientData = (void*) &numData;
180 
181  m_choicePriAxisNumbering->Append( label, clientData );
182  m_choiceSecAxisNumbering->Append( label, clientData );
183  m_choiceCircNumbering->Append( label, clientData );
184  }
185 
186  m_choicePriAxisNumbering->SetSelection( 0 );
187  m_choiceSecAxisNumbering->SetSelection( 0 );
188  m_choiceCircNumbering->SetSelection( 0 );
189 
191 
192  // bind grid options to persister
197 
201 
203 
206 
211 
216 
217  // bind circular options to persister
223 
228 
230 
233 
235 
236  // Run the callbacks once to process the dialog contents
239 
240  m_stdButtonsOK->SetDefault();
241  Fit();
242  SetMinSize( GetSize() );
243 }
244 
245 
246 void DIALOG_CREATE_ARRAY::OnParameterChanged( wxCommandEvent& event )
247 {
250 }
251 
252 
262 static bool validateLongEntry( const wxTextEntry& entry, long& dest, const wxString& description,
263  wxArrayString& errors )
264 {
265  bool ok = true;
266 
267  if( !entry.GetValue().ToLong( &dest ) )
268  {
269  wxString err;
270  err.Printf( _( "Bad numeric value for %s: %s" ), description, entry.GetValue() );
271  errors.Add( err );
272  ok = false;
273  }
274 
275  return ok;
276 }
277 
278 
289 static bool validateAxisOptions( const wxTextCtrl& offsetEntry, const wxChoice& typeEntry,
290  const wxTextCtrl& aStepEntry, ARRAY_AXIS& aAxis,
291  wxArrayString& errors )
292 {
293  void* clientData = typeEntry.GetClientData( typeEntry.GetSelection() );
294  const NUMBERING_LIST_DATA* numberingData = static_cast<NUMBERING_LIST_DATA*>( clientData );
295 
296  wxCHECK_MSG( numberingData, false, "Failed to get client data from list control." );
297 
298  aAxis.SetAxisType( numberingData->m_numbering_type );
299 
300  const wxString text = offsetEntry.GetValue();
301 
302  bool ok = aAxis.SetOffset( text );
303 
304  if( !ok )
305  {
306  errors.Add( wxString::Format( _( "Could not determine numbering start from '%s': "
307  "expected value consistent with alphabet '%s'." ),
308  text,
309  aAxis.GetAlphabet() ) );
310  return false;
311  }
312 
313  long step;
314  ok = validateLongEntry( aStepEntry, step, _( "step value" ), errors );
315 
316  if( ok )
317  aAxis.SetStep( step );
318 
319  return ok;
320 }
321 
322 
324 {
325  std::unique_ptr<ARRAY_OPTIONS> newSettings;
326 
327  wxArrayString errors;
328  const wxWindow* page = m_gridTypeNotebook->GetCurrentPage();
329 
330  if( page == m_gridPanel )
331  {
332  auto newGrid = std::make_unique<ARRAY_GRID_OPTIONS>();
333  bool ok = true;
334 
335  // ints
336  ok = ok && validateLongEntry(*m_entryNx, newGrid->m_nx, _("horizontal count"), errors);
337  ok = ok && validateLongEntry(*m_entryNy, newGrid->m_ny, _("vertical count"), errors);
338 
339  newGrid->m_delta.x = m_hSpacing.GetValue();
340  newGrid->m_delta.y = m_vSpacing.GetValue();
341 
342  newGrid->m_offset.x = m_hOffset.GetValue();
343  newGrid->m_offset.y = m_vOffset.GetValue();
344 
345  ok = ok && validateLongEntry(*m_entryStagger, newGrid->m_stagger, _("stagger"), errors);
346 
347  newGrid->m_stagger_rows = m_radioBoxGridStaggerType->GetSelection() == 0;
348 
349  newGrid->m_horizontalThenVertical = m_radioBoxGridNumberingAxis->GetSelection() == 0;
350  newGrid->m_reverseNumberingAlternate = m_checkBoxGridReverseNumbering->GetValue();
351 
352  newGrid->SetShouldNumber( m_isFootprintEditor );
353 
354  if( m_isFootprintEditor )
355  {
356  newGrid->SetNumberingStartIsSpecified( m_rbGridStartNumberingOpt->GetSelection() == 1 );
357 
358  if( newGrid->GetNumberingStartIsSpecified() )
359  {
360  newGrid->m_2dArrayNumbering = m_radioBoxGridNumberingScheme->GetSelection() != 0;
361 
362  // validate from the input fields
365  newGrid->m_pri_axis, errors );
366 
367  if( newGrid->m_2dArrayNumbering )
368  {
371  newGrid->m_sec_axis, errors )
372  && numOk;
373  }
374 
375  ok = ok && numOk;
376  }
377  else
378  {
379  // artificial linear numeric scheme from 1
380  newGrid->m_2dArrayNumbering = false;
381  newGrid->m_pri_axis.SetAxisType( ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC );
382  newGrid->m_pri_axis.SetOffset( 1 );
383  }
384  }
385 
386  // Only use settings if all values are good
387  if( ok )
388  newSettings = std::move( newGrid );
389  }
390  else if( page == m_circularPanel )
391  {
392  auto newCirc = std::make_unique<ARRAY_CIRCULAR_OPTIONS>();
393  bool ok = true;
394 
395  newCirc->m_centre.x = m_hCentre.GetValue();
396  newCirc->m_centre.y = m_vCentre.GetValue();
397  newCirc->m_angle = DoubleValueFromString( EDA_UNITS::DEGREES,
398  m_entryCircAngle->GetValue() );
399 
400  ok = ok && validateLongEntry(*m_entryCircCount, newCirc->m_nPts, _("point count"), errors);
401 
402  newCirc->m_rotateItems = m_entryRotateItemsCb->GetValue();
403  newCirc->SetShouldNumber( m_isFootprintEditor );
404 
405  if( m_isFootprintEditor )
406  {
407  newCirc->SetNumberingStartIsSpecified( m_rbCircStartNumberingOpt->GetSelection() == 1 );
408 
409  if( newCirc->GetNumberingStartIsSpecified() )
410  {
411  ok = ok
413  *m_entryCircNumberingStep, newCirc->m_axis, errors );
414  }
415  else
416  {
417  // artificial linear numeric scheme from 1
418  newCirc->m_axis.SetAxisType( ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC );
419  newCirc->m_axis.SetOffset( 1 ); // Start at "1"
420  }
421  }
422 
423  // Only use settings if all values are good
424  if( ok )
425  newSettings = std::move( newCirc );
426  }
427 
428  // If we got good settings, send them out and finish
429  if( newSettings )
430  {
431  // assign pointer and ownership here
432  m_settings = std::move( newSettings );
433 
434  m_settings->SetSShouldReannotateFootprints( m_radioBtnUniqueRefs->GetValue() );
435 
436  // persist the control state for next time
438 
439  return true;
440  }
441  else
442  {
443  wxString errorStr;
444 
445  if( errors.IsEmpty() )
446  errorStr = _("Bad parameters");
447  else
448  errorStr = boost::algorithm::join( errors, "\n" );
449 
450  wxMessageBox( errorStr );
451  return false;
452  }
453 }
454 
455 
457 {
458  if( m_isFootprintEditor )
459  {
460  m_footprintReannotatePanel->Show( false );
461 
462  m_gridPadNumberingPanel->Show( true );
463  m_circularPadNumberingPanel->Show( true );
464 
465  // If we set the start number, we can set the other options,
466  // otherwise it's a hardcoded linear array
467  const bool use_set_start_grid = m_rbGridStartNumberingOpt->GetSelection() == 1;
468 
469  m_radioBoxGridNumberingScheme->Enable( use_set_start_grid );
470  m_labelPriAxisNumbering->Enable( use_set_start_grid );
471  m_choicePriAxisNumbering->Enable( use_set_start_grid );
472 
473  // Disable the secondary axis numbering option if the
474  // numbering scheme doesn't have two axes
475  const bool num2d = m_radioBoxGridNumberingScheme->GetSelection() != 0;
476 
477  m_labelSecAxisNumbering->Enable( use_set_start_grid && num2d );
478  m_choiceSecAxisNumbering->Enable( use_set_start_grid && num2d );
479 
480  // We can only set an offset if we're setting the start number
481  m_labelGridNumberingOffset->Enable( use_set_start_grid );
482  m_entryGridPriNumberingOffset->Enable( use_set_start_grid );
483  m_entryGridSecNumberingOffset->Enable( use_set_start_grid && num2d );
484 
485  // disable the circular number offset in the same way
486  const bool use_set_start_circ = m_rbCircStartNumberingOpt->GetSelection() == 1;
487  m_entryCircNumberingStart->Enable( use_set_start_circ );
488  }
489  else
490  {
491  // grid
492  m_rbGridStartNumberingOpt->Enable( false );
493  m_radioBoxGridNumberingScheme->Enable( false );
494 
495  m_labelPriAxisNumbering->Enable( false );
496  m_labelSecAxisNumbering->Enable( false );
497 
498  m_choiceSecAxisNumbering->Enable( false );
499  m_choicePriAxisNumbering->Enable( false );
500 
501  m_labelGridNumberingOffset->Enable( false );
502  m_entryGridPriNumberingOffset->Enable( false );
503  m_entryGridSecNumberingOffset->Enable( false );
504 
505  m_gridPadNumberingPanel->Show( false );
506 
507  // circular
508  m_rbCircStartNumberingOpt->Enable( false );
509  m_entryCircNumberingStart->Enable( false );
510 
511  m_circularPadNumberingPanel->Show( false );
512 
513  m_footprintReannotatePanel->Show( true );
514  }
515 }
516 
517 
519 {
521 
522  // Find the radius, etc of the circle
523  centre -= m_originalItemPosition;
524 
525  m_circRadius.SetValue( int( centre.EuclideanNorm() ) );
526 }
void OnParameterChanged(wxCommandEvent &event) override
Implementation of conversion functions that require both schematic and board internal units.
std::unique_ptr< ARRAY_OPTIONS > & m_settings
The settings to re-seat on dialog OK.
static bool validateLongEntry(const wxTextEntry &entry, long &dest, const wxString &description, wxArrayString &errors)
Validate and save a long integer entry.
void SetAxisType(NUMBERING_TYPE aType)
Set the axis numbering type.
Definition: array_axis.cpp:96
bool SetOffset(const wxString &aOffsetName)
Set the axis start (as a string, which should decode to a valid index in the alphabet)
Definition: array_axis.cpp:102
WIDGET_SAVE_RESTORE m_cfg_persister
CREATE_ARRAY_DIALOG_ENTRIES()
Construct with some sensible defaults.
void SetStep(int aStep)
Set the skip between consecutive numbers (useful when doing a partial array, e.g.
Definition: array_axis.cpp:127
void RestoreConfigToControls()
Restore the values from the internally-stored references to the underlying data to each bound control...
Class that contains information about a single array axis and the numbering of items along that axis.
Definition: array_axis.h:38
#define _(s)
bool TransferDataFromWindow() override
void ReadConfigFromControls()
Read values of all bound controls into the internally-stored references to the underlying data.
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
Struct containing the last-entered values for the dialog.
DIALOG_CREATE_ARRAY(PCB_BASE_FRAME *aParent, std::unique_ptr< ARRAY_OPTIONS > &aOptions, bool enableNumbering, const wxPoint &aOrigPos)
Construct a new dialog.
virtual void SetUnits(EDA_UNITS aUnits)
Normally not needed (as the UNIT_BINDER inherits from the parent frame), but can be used to set to DE...
Definition: unit_binder.cpp:92
void Add(wxRadioBox &ctrl, long &dest)
Bind a radiobox to a choice.
static const std::vector< NUMBERING_LIST_DATA > numberingTypeData
List of type <--> name mappings (in order) for the numbering type list boxes.
Local mapping for list-box <-> numbering type.
static bool validateAxisOptions(const wxTextCtrl &offsetEntry, const wxChoice &typeEntry, const wxTextCtrl &aStepEntry, ARRAY_AXIS &aAxis, wxArrayString &errors)
Validates and saves (if valid) the type and offset of an array axis numbering.
virtual void SetValue(int aValue)
Set new value (in Internal Units) for the text field, taking care of units conversion.
virtual long long int GetValue()
Return the current value in Internal Units.
void SetCoordType(ORIGIN_TRANSFORMS::COORD_TYPES_T aCoordType)
Set the current origin transform mode.
Definition: unit_binder.h:177
ARRAY_AXIS::NUMBERING_TYPE m_numbering_type
Class DIALOG_CREATE_ARRAY_BASE.
double DoubleValueFromString(EDA_UNITS aUnits, const wxString &aTextValue, EDA_DATA_TYPE aType)
Function DoubleValueFromString converts aTextValue to a double.
Definition: base_units.cpp:307
static constexpr int Millimeter2iu(double mm)
const wxString & GetAlphabet() const
Get the alphabet for the current numbering scheme.
Definition: array_axis.cpp:43
static CREATE_ARRAY_DIALOG_ENTRIES s_arrayOptions
Base PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer.
const wxPoint m_originalItemPosition