KiCad PCB EDA Suite
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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 The 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 <wx/msgdlg.h>
27
28#include <base_units.h>
29#include <footprint.h>
30#include <pcb_edit_frame.h>
31#include <tools/pcb_actions.h>
33#include <tool/tool_manager.h>
35
36
41{
47
48 bool m_OptionsSet = true;
49
50 long m_GridNx = 5;
51 long m_GridNy = 5;
52 long m_GridDx = pcbIUScale.mmToIU( 2.54 );
53 long m_GridDy = pcbIUScale.mmToIU( 2.54 );
54 long m_GridOffsetX = 0;
55 long m_GridOffsetY = 0;
56 long m_GridStagger = 1;
57 bool m_GridStaggerRows = true;
59 long m_GridNumberingAxis = 0; // h then v
60 bool m_GridNumReverseAlt = false;
61 long m_GridNumStartSet = 1; // use specified start
62 long m_Grid2dArrayNumbering = 0; // linear numbering
63 long m_GridPrimaryAxisScheme = 0; // numeric
64 long m_GridSecondaryAxisScheme = 0; // numeric
65 wxString m_GridPrimaryNumOffset = wxT( "1" ); // numeric
66 wxString m_GridSecondaryNumOffset = wxT( "1" ); // numeric
69
70 long m_CircCentreX = 0;
71 long m_CircCentreY = 0;
73 long m_CircCount = 4;
75 long m_CircNumStartSet = 1; // use specified start
77 wxString m_CircNumberingOffset = wxT("1");
80 long m_ArrayTypeTab = 0; // start on grid view
81 bool m_SelectionArrange = false;
82 bool m_SelectionDuplicate = true; // Duplicate by default
84 bool m_FootprintReannotate = true; // Assign unique by default
85};
86
87// Persistent options settings
89
94{
96 wxString m_label;
97};
98
103static const std::vector<NUMBERING_LIST_DATA> numberingTypeData {
104 {
106 _( "Numerals (0,1,2,...,9,10)" ),
107 },
108 {
110 _( "Hexadecimal (0,1,...,F,10,...)" ),
111 },
112 {
114 _( "Alphabet, minus IOSQXZ" ),
115 },
116 {
118 _( "Alphabet, full 26 characters" ),
119 },
120};
121
123 std::unique_ptr<ARRAY_OPTIONS>& aSettings,
124 bool aIsFootprintEditor, const VECTOR2I& aOrigPos ) :
125 DIALOG_CREATE_ARRAY_BASE( aParent ),
126 m_frame( aParent ),
127 m_settings( aSettings ),
128 m_originalItemPosition( aOrigPos ), m_isFootprintEditor( aIsFootprintEditor ),
129 m_hSpacing( aParent, m_labelDx, m_entryDx, m_unitLabelDx ),
130 m_vSpacing( aParent, m_labelDy, m_entryDy, m_unitLabelDy ),
131 m_hOffset( aParent, m_labelOffsetX, m_entryOffsetX, m_unitLabelOffsetX ),
132 m_vOffset( aParent, m_labelOffsetY, m_entryOffsetY, m_unitLabelOffsetY ),
133 m_hCentre( aParent, m_labelCentreX, m_entryCentreX, m_unitLabelCentreX ),
134 m_vCentre( aParent, m_labelCentreY, m_entryCentreY, m_unitLabelCentreY ),
135 m_circAngle( aParent, m_labelCircAngle, m_entryCircAngle, m_unitLabelCircAngle ),
136 m_cfg_persister( pcbIUScale, s_arrayOptions.m_OptionsSet )
137{
138 // Configure display origin transforms
145
146 // Set up numbering scheme drop downs character set strings
147 for( const auto& numData : numberingTypeData )
148 {
149 const wxString label = wxGetTranslation( numData.m_label );
150 void* clientData = (void*) &numData;
151
152 m_choicePriAxisNumbering->Append( label, clientData );
153 m_choiceSecAxisNumbering->Append( label, clientData );
154 m_choiceCircNumbering->Append( label, clientData );
155 }
156
157 m_choicePriAxisNumbering->SetSelection( 0 );
158 m_choiceSecAxisNumbering->SetSelection( 0 );
159 m_choiceCircNumbering->SetSelection( 0 );
160
161 m_circAngle.SetUnits( EDA_UNITS::DEGREES );
162
163 // bind grid options to persister
168
172
174
176
179
184
189
190 // bind circular options to persister
193
198
203
205
208
211
213
214 // Run the callbacks once to process the dialog contents
217
219 Fit();
220 SetMinSize( GetSize() );
221}
222
223
225{
226}
227
228
229void DIALOG_CREATE_ARRAY::OnParameterChanged( wxCommandEvent& event )
230{
231 if( m_checkBoxFullCircle->GetValue() && m_entryCircAngle == event.GetEventObject() )
232 {
233 return;
234 }
235
238}
239
240
241void DIALOG_CREATE_ARRAY::OnSelectCenterButton( wxCommandEvent& event )
242{
243 event.Skip();
244
246 wxCHECK( pickerTool, /* void */ );
247
248 if( event.GetEventObject() == m_btnSelectCenterItem )
249 {
252 PCB_PICKER_TOOL::INTERACTIVE_PARAMS{ this, _( "Select center item..." ) } );
253 }
254 else if( event.GetEventObject() == m_btnSelectCenterPoint )
255 {
258 PCB_PICKER_TOOL::INTERACTIVE_PARAMS{ this, _( "Select center point..." ) } );
259 }
260 else
261 {
262 wxFAIL_MSG( "Unknown event source" );
263 }
264
265 // Hide, but do not close, the dialog
266 Hide();
267}
268
269
270void DIALOG_CREATE_ARRAY::OnAxisNumberingChange( wxCommandEvent& aEvent )
271{
272 // On an alphabet change, make sure the offset control is valid by default.
273
274 const int newAlphabet = aEvent.GetSelection();
275
276 wxCHECK( newAlphabet >= 0 && newAlphabet < static_cast<int>( numberingTypeData.size() ),
277 /* void */ );
278
279 const ARRAY_AXIS::NUMBERING_TYPE numberingType =
280 numberingTypeData[newAlphabet].m_numbering_type;
281
282 wxTextCtrl* matchingTextCtrl = nullptr;
283
284 if( aEvent.GetEventObject() == m_choicePriAxisNumbering )
285 matchingTextCtrl = m_entryGridPriNumberingOffset;
286 else if( aEvent.GetEventObject() == m_choiceSecAxisNumbering )
287 matchingTextCtrl = m_entryGridSecNumberingOffset;
288 else if( aEvent.GetEventObject() == m_choiceCircNumbering )
289 matchingTextCtrl = m_entryCircNumberingStart;
290
291 wxCHECK( matchingTextCtrl, /* void */ );
292
293 ARRAY_AXIS dummyAxis;
294 dummyAxis.SetAxisType( numberingType );
295
296 // If the text control has a valid value for the new alphabet, keep it
297 // else reset to the first value in the new alphabet.
298
299 const bool isAlreadyOK = dummyAxis.SetOffset( matchingTextCtrl->GetValue() );
300
301 if( !isAlreadyOK )
302 {
303 dummyAxis.SetOffset( ARRAY_AXIS::TypeIsNumeric( numberingType ) ? 1 : 0 );
304 matchingTextCtrl->SetValue( dummyAxis.GetItemNumber( 0 ) );
305 }
306}
307
308
309// Implement the RECEIVER interface for the callback from the TOOL
311{
312 if( aItem )
313 {
314 m_hCentre.SetValue( aItem->GetPosition().x );
315 m_vCentre.SetValue( aItem->GetPosition().y );
316 }
317
318 Show( true );
319}
320
321
322void DIALOG_CREATE_ARRAY::UpdatePickedPoint( const std::optional<VECTOR2I>& aPoint )
323{
324 if( aPoint )
325 {
326 m_hCentre.SetValue( aPoint->x );
327 m_vCentre.SetValue( aPoint->y );
328 }
329
330 Show( true );
331}
332
333
343static bool validateLongEntry( const wxTextEntry& entry, long& dest, const wxString& description,
344 wxArrayString& errors )
345{
346 bool ok = true;
347
348 if( !entry.GetValue().ToLong( &dest ) )
349 {
350 wxString err;
351 err.Printf( _( "Bad numeric value for %s: %s" ), description, entry.GetValue() );
352 errors.Add( err );
353 ok = false;
354 }
355
356 return ok;
357}
358
359
370static bool validateAxisOptions( const wxTextCtrl& offsetEntry, const wxChoice& typeEntry,
371 const wxTextCtrl& aStepEntry, ARRAY_AXIS& aAxis,
372 wxArrayString& errors )
373{
374 void* clientData = typeEntry.GetClientData( typeEntry.GetSelection() );
375 const NUMBERING_LIST_DATA* numberingData = static_cast<NUMBERING_LIST_DATA*>( clientData );
376
377 wxCHECK_MSG( numberingData, false, wxT( "Failed to get client data from list control." ) );
378
379 aAxis.SetAxisType( numberingData->m_numbering_type );
380
381 const wxString text = offsetEntry.GetValue();
382
383 bool ok = aAxis.SetOffset( text );
384
385 if( !ok )
386 {
387 errors.Add( wxString::Format( _( "Could not determine numbering start from '%s': "
388 "expected value consistent with alphabet '%s'." ),
389 text,
390 aAxis.GetAlphabet() ) );
391 return false;
392 }
393
394 long step;
395 ok = validateLongEntry( aStepEntry, step, _( "step value" ), errors );
396
397 if( ok )
398 aAxis.SetStep( step );
399
400 return ok;
401}
402
403
405{
406 std::unique_ptr<ARRAY_OPTIONS> newSettings;
407
408 wxArrayString errors;
409 const wxWindow* page = m_gridTypeNotebook->GetCurrentPage();
410
411 if( page == m_gridPanel )
412 {
413 auto newGrid = std::make_unique<ARRAY_GRID_OPTIONS>();
414 bool ok = true;
415
416 // ints
417 ok &= validateLongEntry(*m_entryNx, newGrid->m_nx, _("horizontal count"), errors);
418 ok &= validateLongEntry(*m_entryNy, newGrid->m_ny, _("vertical count"), errors);
419
420 newGrid->m_delta.x = m_hSpacing.GetIntValue();
421 newGrid->m_delta.y = m_vSpacing.GetIntValue();
422
423 newGrid->m_offset.x = m_hOffset.GetIntValue();
424 newGrid->m_offset.y = m_vOffset.GetIntValue();
425
426 newGrid->m_centred = m_rbCentreOnSource->GetValue();
427
428 ok &= validateLongEntry(*m_entryStagger, newGrid->m_stagger, _("stagger"), errors);
429
430 newGrid->m_stagger_rows = m_staggerRows->GetValue();
431
432 newGrid->m_horizontalThenVertical = m_radioBoxGridNumberingAxis->GetSelection() == 0;
433 newGrid->m_reverseNumberingAlternate = m_checkBoxGridReverseNumbering->GetValue();
434
435 newGrid->SetShouldNumber( m_isFootprintEditor );
436
438 {
439 newGrid->SetNumberingStartIsSpecified( m_rbGridStartNumberingOpt->GetSelection() == 1 );
440
441 if( newGrid->GetNumberingStartIsSpecified() )
442 {
443 newGrid->m_2dArrayNumbering = m_radioBoxGridNumberingScheme->GetSelection() != 0;
444
445 // validate from the input fields
449 newGrid->m_pri_axis, errors );
450
451 if( newGrid->m_2dArrayNumbering )
452 {
456 newGrid->m_sec_axis, errors );
457 }
458
459 ok &= numOk;
460 }
461 else
462 {
463 // artificial linear numeric scheme from 1
464 newGrid->m_2dArrayNumbering = false;
465 newGrid->m_pri_axis.SetAxisType( ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC );
466 newGrid->m_pri_axis.SetOffset( 1 );
467 }
468 }
469
470 // Only use settings if all values are good
471 if( ok )
472 newSettings = std::move( newGrid );
473 }
474 else if( page == m_circularPanel )
475 {
476 auto newCirc = std::make_unique<ARRAY_CIRCULAR_OPTIONS>();
477 bool ok = true;
479
480 newCirc->m_centre.x = m_hCentre.GetIntValue();
481 newCirc->m_centre.y = m_vCentre.GetIntValue();
482 newCirc->m_angle = EDA_ANGLE( angle, DEGREES_T );
483
484 ok = validateLongEntry(*m_entryCircCount, newCirc->m_nPts, _("point count"), errors);
485
486 newCirc->m_rotateItems = m_entryRotateItemsCb->GetValue();
487 newCirc->SetShouldNumber( m_isFootprintEditor );
488
490 {
491 newCirc->SetNumberingStartIsSpecified( m_rbCircStartNumberingOpt->GetSelection() == 1 );
492
493 if( newCirc->GetNumberingStartIsSpecified() )
494 {
496 *m_entryCircNumberingStep, newCirc->m_axis, errors );
497 }
498 else
499 {
500 // artificial linear numeric scheme from 1
501 newCirc->m_axis.SetAxisType( ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC );
502 newCirc->m_axis.SetOffset( 1 ); // Start at "1"
503 }
504 }
505
506 // Only use settings if all values are good
507 if( ok )
508 newSettings = std::move( newCirc );
509 }
510
511 bool ret = false;
512
513 // If we got good settings, send them out and finish
514 if( newSettings )
515 {
516 // assign pointer and ownership here
517 m_settings = std::move( newSettings );
518
519 m_settings->SetShouldArrangeSelection( m_radioBtnArrangeSelection->GetValue() );
520 m_settings->SetSShouldReannotateFootprints( m_radioBtnUniqueRefs->GetValue() );
521
522 // persist the control state for next time
524
525 ret = true;
526 }
527 else
528 {
529 wxString errorStr;
530
531 if( errors.IsEmpty() )
532 errorStr = _("Bad parameters");
533 else
534 errorStr = wxJoin( errors, '\n' );
535
536 wxMessageBox( errorStr );
537 ret = false;
538 }
539
540 // This dialog is not modal, so close it now if successful
541 if( ret )
542 Close();
543
544 return ret;
545}
546
547
549{
550 if( m_checkBoxFullCircle->GetValue() )
551 {
552 m_entryCircAngle->Disable();
553 }
554 else
555 {
556 m_entryCircAngle->Enable();
557 }
558
560 {
561 m_footprintReannotatePanel->Show( false );
562
563 m_gridPadNumberingPanel->Show( true );
564 m_circularPadNumberingPanel->Show( true );
565
566 // If we set the start number, we can set the other options,
567 // otherwise it's a hardcoded linear array
568 const bool use_set_start_grid = m_rbGridStartNumberingOpt->GetSelection() == 1;
569
570 m_radioBoxGridNumberingScheme->Enable( use_set_start_grid );
571 m_labelPriAxisNumbering->Enable( use_set_start_grid );
572 m_choicePriAxisNumbering->Enable( use_set_start_grid );
573
574 // Disable the secondary axis numbering option if the
575 // numbering scheme doesn't have two axes
576 const bool num2d = m_radioBoxGridNumberingScheme->GetSelection() != 0;
577
578 m_labelSecAxisNumbering->Enable( use_set_start_grid && num2d );
579 m_choiceSecAxisNumbering->Enable( use_set_start_grid && num2d );
580
581 // We can only set an offset if we're setting the start number
582 m_labelGridNumberingOffset->Enable( use_set_start_grid );
583 m_entryGridPriNumberingOffset->Enable( use_set_start_grid );
584 m_entryGridSecNumberingOffset->Enable( use_set_start_grid && num2d );
585 m_entryGridSecNumberingStep->Enable( use_set_start_grid && num2d );
586
587 // disable the circular number offset in the same way
588 const bool use_set_start_circ = m_rbCircStartNumberingOpt->GetSelection() == 1;
589 m_entryCircNumberingStart->Enable( use_set_start_circ );
590 }
591 else
592 {
593 // grid
594 m_rbGridStartNumberingOpt->Enable( false );
595 m_radioBoxGridNumberingScheme->Enable( false );
596
597 m_labelPriAxisNumbering->Enable( false );
598 m_labelSecAxisNumbering->Enable( false );
599
600 m_choiceSecAxisNumbering->Enable( false );
601 m_choicePriAxisNumbering->Enable( false );
602
603 m_labelGridNumberingOffset->Enable( false );
604 m_entryGridPriNumberingOffset->Enable( false );
605 m_entryGridSecNumberingOffset->Enable( false );
606
607 m_gridPadNumberingPanel->Show( false );
608
609 // circular
610 m_rbCircStartNumberingOpt->Enable( false );
611 m_entryCircNumberingStart->Enable( false );
612
613 m_circularPadNumberingPanel->Show( false );
614
615 m_footprintReannotatePanel->Show( true );
616 }
617
618 if( m_radioBtnArrangeSelection->GetValue() )
619 {
620 m_footprintReannotatePanel->Show( false );
621 }
622}
623
624
626{
627 // In full circle mode, the division angle is computed from the number of points
628 if( m_checkBoxFullCircle->GetValue() )
629 {
630 long nPts;
631 if( m_entryCircCount->GetValue().ToLong( &nPts ) )
632 {
633 EDA_ANGLE division = EDA_ANGLE( 360, DEGREES_T ) / nPts;
634 m_circAngle.SetAngleValue( division );
635 }
636 }
637}
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:108
Class that contains information about a single array axis and the numbering of items along that axis.
Definition: array_axis.h:40
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:104
wxString GetItemNumber(int n) const
Get the position number (name) for the n'th axis point.
Definition: array_axis.cpp:135
static bool TypeIsNumeric(NUMBERING_TYPE type)
Check if a numbering type is a numeric type.
Definition: array_axis.h:60
const wxString & GetAlphabet() const
Get the alphabet for the current numbering scheme.
Definition: array_axis.cpp:45
void SetAxisType(NUMBERING_TYPE aType)
Set the axis numbering type.
Definition: array_axis.cpp:98
@ NUMBERING_NUMERIC
Arabic numerals: 0,1,2,3,4,5,6,7,8,9,10,11...
Definition: array_axis.h:44
@ NUMBERING_HEX
Definition: array_axis.h:45
@ NUMBERING_ALPHA_NO_IOSQXZ
Alphabet, excluding IOSQXZ.
Definition: array_axis.h:53
@ NUMBERING_ALPHA_FULL
Full 26-character alphabet.
Definition: array_axis.h:54
void SetStep(int aStep)
Set the skip between consecutive numbers (useful when doing a partial array, e.g.
Definition: array_axis.cpp:129
Class DIALOG_CREATE_ARRAY_BASE.
wxRadioButton * m_radioBtnDuplicateSelection
WIDGET_SAVE_RESTORE m_cfg_persister
DIALOG_CREATE_ARRAY(PCB_BASE_FRAME *aParent, std::unique_ptr< ARRAY_OPTIONS > &aOptions, bool enableNumbering, const VECTOR2I &aOrigPos)
Construct a new dialog.
bool TransferDataFromWindow() override
PCB_BASE_FRAME * m_frame
void OnSelectCenterButton(wxCommandEvent &aEvent) override
std::unique_ptr< ARRAY_OPTIONS > & m_settings
The settings to re-seat on dialog OK.
void OnParameterChanged(wxCommandEvent &aEvent) override
void UpdatePickedItem(const EDA_ITEM *aItem) override
void UpdatePickedPoint(const std::optional< VECTOR2I > &aPoint) override
void OnAxisNumberingChange(wxCommandEvent &aEvent) override
bool Show(bool show) override
void SetupStandardButtons(std::map< int, wxString > aLabels={})
A base class for most all the KiCad significant classes used in schematics and boards.
Definition: eda_item.h:95
virtual VECTOR2I GetPosition() const
Definition: eda_item.h:248
static TOOL_ACTION selectPointInteractively
Definition: pcb_actions.h:333
static TOOL_ACTION selectItemInteractively
Selection of reference points/items.
Definition: pcb_actions.h:332
Base PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer.
Generic tool for picking an item.
TOOL_MANAGER * GetToolManager() const
Return the MVC controller.
Definition: tools_holder.h:55
bool RunAction(const std::string &aActionName, T aParam)
Run the specified action immediately, pausing the current action to run the new one.
Definition: tool_manager.h:150
int GetIntValue()
Definition: unit_binder.h:129
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...
virtual void SetAngleValue(const EDA_ANGLE &aValue)
virtual void SetValue(long long int aValue)
Set new value (in Internal Units) for the text field, taking care of units conversion.
void SetCoordType(ORIGIN_TRANSFORMS::COORD_TYPES_T aCoordType)
Set the current origin transform mode.
Definition: unit_binder.h:200
void Add(wxRadioBox &ctrl, long &dest)
Bind a radiobox to a choice.
void RestoreConfigToControls()
Restore the values from the internally-stored references to the underlying data to each bound control...
void ReadConfigFromControls()
Read values of all bound controls into the internally-stored references to the underlying data.
static const std::vector< NUMBERING_LIST_DATA > numberingTypeData
List of type <--> name mappings (in order) for the numbering type list boxes.
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.
static bool validateLongEntry(const wxTextEntry &entry, long &dest, const wxString &description, wxArrayString &errors)
Validate and save a long integer entry.
static CREATE_ARRAY_DIALOG_ENTRIES s_arrayOptions
#define _(s)
static constexpr EDA_ANGLE ANGLE_90
Definition: eda_angle.h:403
@ DEGREES_T
Definition: eda_angle.h:31
KICOMMON_API double DoubleValueFromString(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, const wxString &aTextValue, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
Convert aTextValue to a double.
Definition: eda_units.cpp:497
Struct containing the last-entered values for the dialog.
CREATE_ARRAY_DIALOG_ENTRIES()
Construct with some sensible defaults.
constexpr int mmToIU(double mm) const
Definition: base_units.h:88
Local mapping for list-box <-> numbering type.
ARRAY_AXIS::NUMBERING_TYPE m_numbering_type