KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_properties_panel.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) 2020-2023 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <[email protected]>
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 3
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, see <https://www.gnu.org/licenses/>.
20 */
21
23
24#include <font/fontconfig.h>
26#include <frame_type.h>
27#include <pgm_base.h>
28#include <pcb_base_edit_frame.h>
29#include <tool/tool_manager.h>
30#include <tools/pcb_actions.h>
32#include <tools/edit_tool.h>
39#include <math/util.h>
40#include <base_units.h>
41#include <pcb_generated_table.h>
42#include <pcb_drill_map.h>
43#include <pcb_shape.h>
44#include <eda_units.h>
48#include <board_commit.h>
50#include <board.h>
52#include <properties/property.h>
53#include <pcb_dimension.h>
54#include <pcb_shape.h>
55#include <pcb_text.h>
56#include <pcb_track.h>
57#include <pcb_generator.h>
60#include <pad.h>
61#include <footprint.h>
62#include <pcb_field.h>
63#include <template_fieldnames.h>
65#include <string_utils.h>
67#include <widgets/ui_common.h>
68#include <widgets/unit_binder.h>
69
70#include <memory>
71#include <vector>
72#include <wx/combobox.h>
73
74#include <wx/msgdlg.h>
75
76#include <cmath>
77
78
79class PG_NET_SELECTOR_EDITOR : public wxPGEditor
80{
81public:
82 static const wxString EDITOR_NAME;
83
85
86 wxString GetName() const override { return EDITOR_NAME; }
87
88 wxPGWindowList CreateControls( wxPropertyGrid* aGrid, wxPGProperty* aProperty, const wxPoint& aPos,
89 const wxSize& aSize ) const override
90 {
91 NET_SELECTOR* editor = new NET_SELECTOR( aGrid->GetPanel(), wxID_ANY, aPos, aSize, 0 );
92
93 // wxPropertyGrid registers editors globally and the same PG_NET_SELECTOR_EDITOR
94 // instance is reused by every PCB_PROPERTIES_PANEL (board editor, footprint editor).
95 // Resolve the owning panel -- and therefore the live frame and board -- from the
96 // grid at use time instead of caching frame state on the editor. This avoids
97 // cross-frame state corruption and nullptr derefs when one panel is destroyed while
98 // another is still live.
99 if( PCB_PROPERTIES_PANEL* panel = dynamic_cast<PCB_PROPERTIES_PANEL*>( aGrid->GetParent() ) )
100 {
101 if( PCB_BASE_EDIT_FRAME* frame = panel->GetFrame() )
102 {
103 if( BOARD* board = frame->GetBoard() )
104 editor->SetNetInfo( &board->GetNetInfo() );
105 }
106 }
107
108 editor->SetIndeterminateString( INDETERMINATE_STATE );
109 UpdateControl( aProperty, editor );
110
111 editor->Bind( FILTERED_ITEM_SELECTED,
112 [=]( wxCommandEvent& aEvt )
113 {
114 auto& choices = const_cast<wxPGChoices&>( aProperty->GetChoices() );
115 wxString netname = editor->GetSelectedNetname();
116
117 if( choices.Index( netname ) == wxNOT_FOUND )
118 choices.Add( netname, editor->GetSelectedNetcode() );
119
120 wxVariant val( editor->GetSelectedNetcode() );
121 aGrid->ChangePropertyValue( aProperty, val );
122 } );
123
124 return editor;
125 }
126
127 void UpdateControl( wxPGProperty* aProperty, wxWindow* aCtrl ) const override
128 {
129 if( NET_SELECTOR* editor = dynamic_cast<NET_SELECTOR*>( aCtrl ) )
130 {
131 if( aProperty->IsValueUnspecified() )
132 editor->SetIndeterminate();
133 else
134 editor->SetSelectedNetcode( (int) aProperty->GetValue().GetLong() );
135 }
136 }
137
138 bool GetValueFromControl( wxVariant& aVariant, wxPGProperty* aProperty, wxWindow* aCtrl ) const override
139 {
140 NET_SELECTOR* editor = dynamic_cast<NET_SELECTOR*>( aCtrl );
141
142 if( !editor )
143 return false;
144
145 aVariant = static_cast<long>( editor->GetSelectedNetcode() );
146 return true;
147 }
148
149 bool OnEvent( wxPropertyGrid* aGrid, wxPGProperty* aProperty, wxWindow* aWindow, wxEvent& aEvent ) const override
150 {
151 return false;
152 }
153};
154
155
156const wxString PG_NET_SELECTOR_EDITOR::EDITOR_NAME = wxS( "PG_NET_SELECTOR_EDITOR" );
157
158class PG_TRACK_WIDTH_EDITOR : public wxPGEditor
159{
160public:
161 static const wxString EDITOR_NAME;
162
164 m_frame( aFrame )
165 {
166 if( m_frame )
167 {
168 m_unitBinder = std::make_unique<PROPERTY_EDITOR_UNIT_BINDER>( m_frame );
169 m_unitBinder->SetUnits( m_frame->GetUserUnits() );
170 }
171
173 }
174
175 wxString GetName() const override { return m_editorName; }
176
177 static wxString BuildEditorName( PCB_BASE_EDIT_FRAME* aFrame )
178 {
179 if( !aFrame )
180 return EDITOR_NAME + "NoFrame";
181
182 return EDITOR_NAME + aFrame->GetName();
183 }
184
186 {
187 m_frame = aFrame;
188
189 if( m_frame )
190 {
191 m_unitBinder = std::make_unique<PROPERTY_EDITOR_UNIT_BINDER>( m_frame );
192 m_unitBinder->SetUnits( m_frame->GetUserUnits() );
193 }
194 else
195 {
196 m_unitBinder = nullptr;
197 }
198 }
199
200 wxPGWindowList CreateControls( wxPropertyGrid* aGrid, wxPGProperty* aProperty, const wxPoint& aPos,
201 const wxSize& aSize ) const override
202 {
203 wxASSERT( m_unitBinder );
204
205 wxComboBox* editor = new wxComboBox( aGrid->GetPanel(), wxID_ANY, wxEmptyString, aPos, aSize, 0, nullptr,
206 wxCB_DROPDOWN | wxTE_PROCESS_ENTER );
207
208 m_unitBinder->SetControl( editor );
209 m_unitBinder->RequireEval();
210 m_unitBinder->SetUnits( m_frame->GetUserUnits() );
211
213 UpdateControl( aProperty, editor );
214
215 std::shared_ptr<bool> popupShown = std::make_shared<bool>( false );
216 auto commitValue =
217 [this, aGrid, aProperty, editor]()
218 {
219 if( !m_unitBinder || editor->GetValue() == INDETERMINATE_STATE )
220 return;
221
222 wxVariant val( static_cast<long>( m_unitBinder->GetValue() ) );
223 aGrid->ChangePropertyValue( aProperty, val );
224 };
225
226 editor->Bind( wxEVT_COMBOBOX_DROPDOWN,
227 [popupShown]( wxCommandEvent& aEvent )
228 {
229 *popupShown = true;
230 aEvent.Skip();
231 } );
232
233 editor->Bind( wxEVT_COMBOBOX,
234 [commitValue, popupShown]( wxCommandEvent& aEvent )
235 {
236 // Choosing a preset from the dropdown should apply that preset immediately.
237 if( *popupShown )
238 commitValue();
239
240 aEvent.Skip();
241 } );
242
243 editor->Bind( wxEVT_COMBOBOX_CLOSEUP,
244 [aGrid, popupShown]( wxCommandEvent& aEvent )
245 {
246 aGrid->CallAfter( [popupShown]()
247 {
248 *popupShown = false;
249 } );
250
251 aEvent.Skip();
252 } );
253
254 editor->Bind( wxEVT_CHAR_HOOK,
255 [commitValue, editor, popupShown]( wxKeyEvent& aEvent )
256 {
257 // Pressing Enter after typing a custom value should apply the typed value,
258 // not the first preset in the dropdown.
259 if( ( aEvent.GetKeyCode() == WXK_RETURN
260 || aEvent.GetKeyCode() == WXK_NUMPAD_ENTER )
261 && !*popupShown )
262 {
263 if( editor->GetValue() != INDETERMINATE_STATE )
264 {
265 commitValue();
266 return;
267 }
268
269 // Let the property grid accept an unchanged mixed value.
270 }
271
272 aEvent.Skip();
273 } );
274
275 editor->Bind( wxEVT_KILL_FOCUS,
276 [commitValue, popupShown]( wxFocusEvent& aEvent )
277 {
278 // Clicking into another property cell should keep any typed custom value.
279 if( !*popupShown )
280 commitValue();
281
282 aEvent.Skip();
283 } );
284
285 return wxPGWindowList( editor, nullptr );
286 }
287
288 void UpdateControl( wxPGProperty* aProperty, wxWindow* aCtrl ) const override
289 {
290 if( !m_unitBinder )
291 return;
292
293 wxComboBox* editor = dynamic_cast<wxComboBox*>( aCtrl );
294 wxCHECK( editor, /* void */ );
295
296 if( aProperty->IsValueUnspecified() )
297 editor->ChangeValue( INDETERMINATE_STATE );
298 else
299 m_unitBinder->ChangeValue( aProperty->GetValue().GetLong() );
300 }
301
302 bool GetValueFromControl( wxVariant& aVariant, wxPGProperty* aProperty, wxWindow* aCtrl ) const override
303 {
304 if( !m_unitBinder )
305 return false;
306
307 wxComboBox* editor = dynamic_cast<wxComboBox*>( aCtrl );
308 wxCHECK_MSG( editor, false, "PG_TRACK_WIDTH_EDITOR requires a combo box!" );
309
310 if( editor->GetValue() == INDETERMINATE_STATE )
311 {
312 aVariant.MakeNull();
313 return true;
314 }
315
316 long result = static_cast<long>( m_unitBinder->GetValue() );
317 bool changed = aVariant.IsNull() || result != aVariant.GetLong();
318
319 if( changed )
320 aVariant = result;
321
322 return changed;
323 }
324
325 bool OnEvent( wxPropertyGrid* aGrid, wxPGProperty* aProperty, wxWindow* aWindow, wxEvent& aEvent ) const override
326 {
327 return false;
328 }
329
330private:
331 void setTrackWidthOptions( wxComboBox* aEditor ) const
332 {
333 std::vector<long long int> trackWidths;
334
335 // 0 is the netclass place-holder.
336 for( unsigned ii = 1; ii < m_frame->GetDesignSettings().m_TrackWidthList.size(); ++ii )
337 trackWidths.push_back( m_frame->GetDesignSettings().m_TrackWidthList[ii] );
338
339 m_unitBinder->SetOptionsList( trackWidths );
340
341 wxString unitLabel = EDA_UNIT_UTILS::GetLabel( m_frame->GetUserUnits() );
342
343 for( unsigned ii = 0; ii < aEditor->GetCount(); ++ii )
344 aEditor->SetString( ii, aEditor->GetString( ii ) + wxS( " " ) + unitLabel );
345 }
346
348 std::unique_ptr<PROPERTY_EDITOR_UNIT_BINDER> m_unitBinder;
349 wxString m_editorName;
350};
351
352
353const wxString PG_TRACK_WIDTH_EDITOR::EDITOR_NAME = wxS( "PG_TRACK_WIDTH_EDITOR" );
354
356 PROPERTIES_PANEL( aParent, aFrame ),
357 m_frame( aFrame ),
358 m_propMgr( PROPERTY_MANAGER::Instance() ),
359 m_scaleConfirmPending( false )
360{
361 addCategoryButton( _HKI( "Custom Properties" ), _( "Add Custom Property" ), BITMAPS::small_plus,
362 [this]()
363 {
365 } );
366
367 addCategoryButton( _HKI( "Fields" ), _( "Add Field" ), BITMAPS::small_plus,
368 [this]()
369 {
371 } );
372
373 m_propMgr.Rebuild();
374 bool found = false;
375
376 wxASSERT( wxPGGlobalVars );
377
378 wxString editorKey = PG_UNIT_EDITOR::BuildEditorName( m_frame );
379
380 auto it = wxPGGlobalVars->m_mapEditorClasses.find( editorKey );
381
382 if( it != wxPGGlobalVars->m_mapEditorClasses.end() )
383 {
384 m_unitEditorInstance = static_cast<PG_UNIT_EDITOR*>( it->second );
385 m_unitEditorInstance->UpdateFrame( m_frame );
386 found = true;
387 }
388
389 if( !found )
390 {
391 PG_UNIT_EDITOR* new_editor = new PG_UNIT_EDITOR( m_frame );
392 m_unitEditorInstance = static_cast<PG_UNIT_EDITOR*>( wxPropertyGrid::RegisterEditorClass( new_editor ) );
393 }
394
395 it = wxPGGlobalVars->m_mapEditorClasses.find( PG_TRACK_WIDTH_EDITOR::BuildEditorName( m_frame ) );
396
397 if( it != wxPGGlobalVars->m_mapEditorClasses.end() )
398 {
399 m_trackWidthEditorInstance = static_cast<PG_TRACK_WIDTH_EDITOR*>( it->second );
400 m_trackWidthEditorInstance->UpdateFrame( m_frame );
401 }
402 else
403 {
404 PG_TRACK_WIDTH_EDITOR* trackWidthEditor = new PG_TRACK_WIDTH_EDITOR( m_frame );
406 static_cast<PG_TRACK_WIDTH_EDITOR*>( wxPropertyGrid::RegisterEditorClass( trackWidthEditor ) );
407 }
408
409 it = wxPGGlobalVars->m_mapEditorClasses.find( PG_CHECKBOX_EDITOR::EDITOR_NAME );
410
411 if( it == wxPGGlobalVars->m_mapEditorClasses.end() )
412 {
413 PG_CHECKBOX_EDITOR* cbEditor = new PG_CHECKBOX_EDITOR();
414 m_checkboxEditorInstance = static_cast<PG_CHECKBOX_EDITOR*>( wxPropertyGrid::RegisterEditorClass( cbEditor ) );
415 }
416 else
417 {
418 m_checkboxEditorInstance = static_cast<PG_CHECKBOX_EDITOR*>( it->second );
419 }
420
421 it = wxPGGlobalVars->m_mapEditorClasses.find( PG_RATIO_EDITOR::EDITOR_NAME );
422
423 if( it == wxPGGlobalVars->m_mapEditorClasses.end() )
424 {
425 PG_RATIO_EDITOR* ratioEditor = new PG_RATIO_EDITOR();
426 m_ratioEditorInstance = static_cast<PG_RATIO_EDITOR*>( wxPropertyGrid::RegisterEditorClass( ratioEditor ) );
427 }
428 else
429 {
430 m_ratioEditorInstance = static_cast<PG_RATIO_EDITOR*>( it->second );
431 }
432
433 it = wxPGGlobalVars->m_mapEditorClasses.find( PG_NET_SELECTOR_EDITOR::EDITOR_NAME );
434
435 if( it == wxPGGlobalVars->m_mapEditorClasses.end() )
436 {
438 m_netSelectorEditorInstance = static_cast<PG_NET_SELECTOR_EDITOR*>( wxPropertyGrid::RegisterEditorClass( netEditor ) );
439 }
440 else
441 {
442 m_netSelectorEditorInstance = static_cast<PG_NET_SELECTOR_EDITOR*>( it->second );
443 }
444
445 it = wxPGGlobalVars->m_mapEditorClasses.find( PG_FPID_EDITOR::BuildEditorName( m_frame ) );
446
447 if( it != wxPGGlobalVars->m_mapEditorClasses.end() )
448 {
449 m_fpEditorInstance = static_cast<PG_FPID_EDITOR*>( it->second );
450 m_fpEditorInstance->UpdateFrame( m_frame );
451 }
452 else
453 {
454 PG_FPID_EDITOR* fpEditor = new PG_FPID_EDITOR( m_frame,
455 []()
456 {
457 return "";
458 });
459 m_fpEditorInstance = static_cast<PG_FPID_EDITOR*>( wxPropertyGrid::RegisterEditorClass( fpEditor ) );
460 }
461
462 it = wxPGGlobalVars->m_mapEditorClasses.find( PG_URL_EDITOR::BuildEditorName( m_frame ) );
463
464 if( it != wxPGGlobalVars->m_mapEditorClasses.end() )
465 {
466 m_urlEditorInstance = static_cast<PG_URL_EDITOR*>( it->second );
467 m_urlEditorInstance->UpdateFrame( m_frame );
468 }
469 else
470 {
471 PG_URL_EDITOR* urlEditor = new PG_URL_EDITOR( m_frame );
472 m_urlEditorInstance = static_cast<PG_URL_EDITOR*>( wxPropertyGrid::RegisterEditorClass( urlEditor ) );
473 }
474
475 Bind( wxEVT_MENU, &PCB_PROPERTIES_PANEL::onContextMenu, this, ID_CTX_ADD_FIELD );
479}
480
481
483{
484 m_unitEditorInstance->UpdateFrame( nullptr );
485 m_fpEditorInstance->UpdateFrame( nullptr );
486 m_urlEditorInstance->UpdateFrame( nullptr );
487 m_trackWidthEditorInstance->UpdateFrame( nullptr );
488
489 // Note: the shared PG_NET_SELECTOR_EDITOR does not cache frame state; it resolves the
490 // owning panel from the property grid on each CreateControls call, so no teardown is
491 // needed here.
492}
493
494
496{
497 PCB_SELECTION_TOOL* selectionTool = m_frame->GetToolManager()->GetTool<PCB_SELECTION_TOOL>();
498 const SELECTION& selection = selectionTool->GetSelection();
499
500 if( selection.Empty() && m_frame->IsType( FRAME_FOOTPRINT_EDITOR ) )
501 {
502 if( BOARD* board = m_frame->GetBoard() )
503 {
504 if( FOOTPRINT* footprint = board->GetFirstFootprint() )
505 {
506 aFallbackSelection.Clear();
507 aFallbackSelection.Add( footprint );
508 return aFallbackSelection;
509 }
510 }
511 }
512
513 return selection;
514}
515
516
518{
519 SELECTION fallbackSelection;
520 const SELECTION& selection = getSelection( fallbackSelection );
521
522 return selection.Empty() ? nullptr : selection.Front();
523}
524
525
527{
528 BOARD* board = m_frame->GetBoard();
529
530 if( !board )
531 return;
532
533 SELECTION fallbackSelection;
534 const SELECTION& selection = getSelection( fallbackSelection );
535
536 // TODO perhaps it could be called less often? use PROPERTIES_TOOL and catch MODEL_RELOAD?
537 updateLists( board );
538
539 // Will actually just be updatePropertyValues() if selection hasn't changed
540 rebuildProperties( selection );
541}
542
543
545{
546 if( !m_frame->GetBoard() )
547 return;
548
549 SELECTION fallbackSelection;
550 const SELECTION& selection = getSelection( fallbackSelection );
551
552 rebuildProperties( selection );
553}
554
555
556bool PCB_PROPERTIES_PANEL::isKeyEditable( const wxPGProperty* aPGProp ) const
557{
558 PROPERTY_BASE* prop = static_cast<PROPERTY_BASE*>( aPGProp->GetClientData() );
559
560 if( !prop )
561 return false;
562
563 EDA_ITEM* item = const_cast<PCB_PROPERTIES_PANEL*>( this )->getFrontItem();
564
565 if( !item )
566 return false;
567
568 if( prop->Group() == _HKI( "Custom Properties" ) )
569 return true;
570
571 if( item->Type() != PCB_FOOTPRINT_T )
572 return false;
573
574 PCB_FIELD* field = static_cast<FOOTPRINT*>( item )->GetField( prop->Name() );
575
576 return field && !field->IsMandatory() && !field->IsPrivate();
577}
578
579
580bool PCB_PROPERTIES_PANEL::isKeyNameInUse( const wxString& aName ) const
581{
582 EDA_ITEM* item = const_cast<PCB_PROPERTIES_PANEL*>( this )->getFrontItem();
583
584 if( !item )
585 return false;
586
587 return m_propMgr.GetProperty( item, aName ) != nullptr;
588}
589
590
591void PCB_PROPERTIES_PANEL::onKeyRenamed( const wxString& aOldName, const wxString& aNewName )
592{
593 SELECTION fallbackSelection;
594 const SELECTION& selection = getSelection( fallbackSelection );
595
596 BOARD_COMMIT changes( m_frame );
597 PROPERTY_COMMIT_HANDLER handler( &changes );
598
599 for( EDA_ITEM* item : selection )
600 {
601 if( !item->IsBOARD_ITEM() )
602 continue;
603
604 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
605
606 if( boardItem->Type() == PCB_FOOTPRINT_T )
607 {
608 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( boardItem );
609 PCB_FIELD* field = footprint->GetField( aOldName );
610
611 if( field && !field->IsMandatory() )
612 {
613 changes.Modify( footprint, nullptr, RECURSE_MODE::NO_RECURSE );
614 field->SetName( aNewName );
615 continue;
616 }
617 }
618
619 wxString value;
620
621 if( boardItem->GetCustomProperty( aOldName, value ) )
622 {
623 changes.Modify( boardItem, nullptr, RECURSE_MODE::NO_RECURSE );
624 boardItem->RemoveCustomProperty( aOldName );
625 boardItem->SetCustomProperty( aNewName, value );
626 }
627 }
628
629 changes.Push( _( "Rename Property" ) );
630
631 AfterCommit();
632}
633
634
635bool PCB_PROPERTIES_PANEL::buildContextMenu( wxMenu& aMenu, wxPGProperty* aPGProp )
636{
637 if( aPGProp->IsCategory() )
638 {
639 if( aPGProp->GetLabel() == wxGetTranslation( _HKI( "Fields" ) ) )
640 aMenu.Append( ID_CTX_ADD_FIELD, _( "Add Field" ) );
641 else if( aPGProp->GetLabel() == wxGetTranslation( _HKI( "Custom Properties" ) ) )
642 aMenu.Append( ID_CTX_ADD_CUSTOM_PROPERTY, _( "Add Custom Property" ) );
643 }
644 else
645 {
646 PROPERTY_BASE* prop = static_cast<PROPERTY_BASE*>( aPGProp->GetClientData() );
647
648 if( !prop )
649 return false;
650
651 if( prop->Group() == _HKI( "Fields" ) )
652 {
653 if( isKeyEditable( aPGProp ) )
654 aMenu.Append( ID_CTX_REMOVE_FIELD, _( "Remove Field" ) );
655
656 aMenu.Append( ID_CTX_ADD_FIELD, _( "Add Field" ) );
657 }
658 else if( prop->Group() == _HKI( "Custom Properties" ) )
659 {
660 aMenu.Append( ID_CTX_REMOVE_CUSTOM_PROPERTY, _( "Remove Custom Property" ) );
661 aMenu.Append( ID_CTX_ADD_CUSTOM_PROPERTY, _( "Add Custom Property" ) );
662 }
663 }
664
665 return aMenu.GetMenuItemCount() > 0;
666}
667
668
669void PCB_PROPERTIES_PANEL::onContextMenu( wxCommandEvent& aEvent )
670{
671 switch( aEvent.GetId() )
672 {
673 case ID_CTX_ADD_FIELD: addBlankField(); break;
677 default:
678 break;
679 }
680}
681
682
684{
685 SELECTION fallbackSelection;
686 const SELECTION& selection = getSelection( fallbackSelection );
687
689
690 if( selection.Empty() )
691 return;
692
693 // Pick a unique untranslated placeholder name that doesn't collide with an existing field.
694 wxString name;
695
696 for( int n = 0; ; ++n )
697 {
699 bool used = false;
700
701 for( EDA_ITEM* item : selection )
702 {
703 if( item->Type() == PCB_FOOTPRINT_T && static_cast<FOOTPRINT*>( item )->HasField( name ) )
704 {
705 used = true;
706 break;
707 }
708 }
709
710 if( !used )
711 break;
712 }
713
714 BOARD_COMMIT changes( m_frame );
715 PROPERTY_COMMIT_HANDLER handler( &changes );
716
717 for( EDA_ITEM* item : selection )
718 {
719 if( item->Type() != PCB_FOOTPRINT_T )
720 continue;
721
722 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
723 PCB_FIELD* field = new PCB_FIELD( footprint, FIELD_T::USER, name );
724
725 field->SetText( wxEmptyString );
726 field->SetVisible( false );
727 changes.Modify( footprint, nullptr, RECURSE_MODE::NO_RECURSE );
728 footprint->Add( field );
729 }
730
731 changes.Push( _( "Add Field" ) );
732 AfterCommit();
733
735
736 beginLabelEdit( name, true );
737}
738
739
741{
742 SELECTION fallbackSelection;
743 const SELECTION& selection = getSelection( fallbackSelection );
744
746
747 if( selection.Empty() )
748 return;
749
750 wxString name;
751
752 for( int n = 0; ; ++n )
753 {
754 name = wxString::Format( wxS( "Property%d" ), n );
755 bool used = false;
756
757 for( EDA_ITEM* item : selection )
758 {
759 wxString dummy;
760
761 if( item->GetCustomProperty( name, dummy ) )
762 {
763 used = true;
764 break;
765 }
766 }
767
768 if( !used )
769 break;
770 }
771
772 BOARD_COMMIT changes( m_frame );
773 PROPERTY_COMMIT_HANDLER handler( &changes );
774
775 for( EDA_ITEM* item : selection )
776 {
777 if( item->IsBOARD_ITEM() )
778 {
779 changes.Modify( item, nullptr, RECURSE_MODE::NO_RECURSE );
780 item->SetCustomProperty( name, wxEmptyString );
781 }
782 }
783
784 changes.Push( _( "Add Custom Property" ) );
785 AfterCommit();
786
788
789 beginLabelEdit( name, true );
790}
791
792
793void PCB_PROPERTIES_PANEL::removeField( const wxString& aName )
794{
795 SELECTION fallbackSelection;
796 const SELECTION& selection = getSelection( fallbackSelection );
797
798 BOARD_COMMIT changes( m_frame );
799 PROPERTY_COMMIT_HANDLER handler( &changes );
800
801 for( EDA_ITEM* item : selection )
802 {
803 if( item->Type() != PCB_FOOTPRINT_T )
804 continue;
805
806 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
807 PCB_FIELD* field = footprint->GetField( aName );
808
809 if( field && !field->IsMandatory() )
810 {
811 // BOARD_COMMIT's own field removal path only hides the field (e.g. when a user presses the delete key),
812 // we want to actually delete a custom field from this explicit menu action
813 changes.Modify( footprint, nullptr, RECURSE_MODE::NO_RECURSE );
814 footprint->Remove( field );
815 }
816 }
817
818 changes.Push( _( "Remove Field" ) );
819 AfterCommit();
820}
821
822
824{
825 SELECTION fallbackSelection;
826 const SELECTION& selection = getSelection( fallbackSelection );
827
828 BOARD_COMMIT changes( m_frame );
829 PROPERTY_COMMIT_HANDLER handler( &changes );
830
831 for( EDA_ITEM* item : selection )
832 {
833 if( item->IsBOARD_ITEM() )
834 {
835 changes.Modify( item, nullptr, RECURSE_MODE::NO_RECURSE );
836 item->RemoveCustomProperty( aName );
837 }
838 }
839
840 changes.Push( _( "Remove Custom Property" ) );
841 AfterCommit();
842}
843
844
846{
847 // Note: currently assuming that any newly-created item from the panel is either a field or
848 // a custom property because those are the types we currently support.
849 if( EDA_ITEM* item = getFrontItem();
850 item && item->Type() == PCB_FOOTPRINT_T && static_cast<FOOTPRINT*>( item )->HasField( aKey ) )
851 {
852 removeField( aKey );
853 }
854 else
855 {
856 removeCustomProperty( aKey );
857 }
858}
859
860
862{
863 SELECTION filtered;
864 filtered.SetIsHover( aSelection.IsHover() );
865
866 for( EDA_ITEM* item : aSelection )
867 {
868 if( item->IsBOARD_ITEM() )
869 {
870 EDA_GROUP* parent = static_cast<BOARD_ITEM*>( item )->GetParentGroup();
871
872 if( parent && parent->AsEdaItem()->Type() == PCB_GENERATOR_T
873 && static_cast<PCB_GENERATOR*>( parent->AsEdaItem() )->ChildrenAreReadOnly() )
874 {
875 continue;
876 }
877 }
878
879 filtered.Add( item );
880 }
881
882 return filtered;
883}
884
885
887{
888 // Strip read-only generator children (like stitch vias)
889 SELECTION editableSelection = filterOutReadOnlyGenChildren( aRawSelection );
890 const SELECTION& aSelection = editableSelection;
891
893
894}
895
896
897wxPGProperty* PCB_PROPERTIES_PANEL::createPGProperty( const PROPERTY_BASE* aProperty ) const
898{
899 if( aProperty->TypeHash() == TYPE_HASH( PCB_LAYER_ID ) )
900 {
901 wxASSERT( aProperty->HasChoices() );
902
903 const wxPGChoices& canonicalLayers = aProperty->Choices();
904 wxArrayString boardLayerNames;
905 wxArrayInt boardLayerIDs;
906
907 for( int ii = 0; ii < (int) aProperty->Choices().GetCount(); ++ii )
908 {
909 int layer = canonicalLayers.GetValue( ii );
910
911 boardLayerNames.push_back( m_frame->GetBoard()->GetLayerName( ToLAYER_ID( layer ) ) );
912 boardLayerIDs.push_back( canonicalLayers.GetValue( ii ) );
913 }
914
915 auto ret = new PGPROPERTY_COLORENUM( new wxPGChoices( boardLayerNames, boardLayerIDs ) );
916
917 ret->SetColorFunc(
918 [&]( int aValue ) -> wxColour
919 {
920 return m_frame->GetColorSettings()->GetColor( ToLAYER_ID( aValue ) ).ToColour();
921 } );
922
923 ret->SetLabel( wxGetTranslation( aProperty->Name() ) );
924 ret->SetName( aProperty->Name() );
925 ret->SetHelpString( wxGetTranslation( aProperty->Name() ) );
926 ret->SetClientData( const_cast<PROPERTY_BASE*>( aProperty ) );
927
928 return ret;
929 }
930
931 wxPGProperty* prop = PGPropertyFactory( aProperty, m_frame );
932
934 prop->SetEditor( PG_FPID_EDITOR::BuildEditorName( m_frame ) );
935 else if( aProperty->Name() == GetDefaultFieldName( FIELD_T::DATASHEET, UNTRANSLATED ) )
936 prop->SetEditor( PG_URL_EDITOR::BuildEditorName( m_frame ) );
937 // OwnerHash is the class that registered the property. Routed PCB_ARC items inherit
938 // PCB_TRACK::Width, so this catches track arcs without changing unrelated "Width" properties.
939 else if( aProperty->OwnerHash() == TYPE_HASH( PCB_TRACK ) && aProperty->Name() == _HKI( "Width" ) )
941
942 return prop;
943}
944
945
946PROPERTY_BASE* PCB_PROPERTIES_PANEL::getPropertyFromEvent( const wxPropertyGridEvent& aEvent ) const
947{
948 EDA_ITEM* item = const_cast<PCB_PROPERTIES_PANEL*>( this )->getFrontItem();
949
950 if( !item || !item->IsBOARD_ITEM() )
951 return nullptr;
952
953 BOARD_ITEM* firstItem = static_cast<BOARD_ITEM*>( item );
954
955 wxCHECK_MSG( firstItem, nullptr, wxT( "getPropertyFromEvent for a property with nothing selected!") );
956
957 PROPERTY_BASE* property = m_propMgr.GetProperty( firstItem, aEvent.GetPropertyName() );
958 wxCHECK_MSG( property, nullptr, wxT( "getPropertyFromEvent for a property not found on the selected item!" ) );
959
960 return property;
961}
962
963
964void PCB_PROPERTIES_PANEL::valueChanging( wxPropertyGridEvent& aEvent )
965{
967 return;
968
969 EDA_ITEM* item = getFrontItem();
970
971 PROPERTY_BASE* property = getPropertyFromEvent( aEvent );
972 wxCHECK( property, /* void */ );
973 wxCHECK( item, /* void */ );
974
975 wxVariant newValue = aEvent.GetPropertyValue();
976
977 if( VALIDATOR_RESULT validationFailure = property->Validate( newValue.GetAny(), item ) )
978 {
979 wxString errorMsg = wxString::Format( wxS( "%s: %s" ), wxGetTranslation( property->Name() ),
980 validationFailure->get()->Format( m_frame ) );
981 m_frame->ShowInfoBarError( errorMsg );
982 aEvent.Veto();
983 return;
984 }
985
986 // Scaling a footprint that has pads is dangerous (the physical
987 // part keeps its original size), so confirm before applying.
988 const wxString propName = aEvent.GetPropertyName();
989
990 if( propName == _HKI( "Scale X" ) || propName == _HKI( "Scale Y" ) )
991 {
992 const double newScale = newValue.GetDouble();
993
994 // Zero, negative, and non-finite scales make the footprint transform degenerate.
995 if( !std::isfinite( newScale ) || newScale <= 0.0 )
996 {
997 m_frame->ShowInfoBarError( _( "Scale must be a positive number." ) );
998 aEvent.Veto();
999 return;
1000 }
1001
1002 if( newScale != 1.0 )
1003 {
1004 SELECTION fallbackSelection;
1005 const SELECTION& selection = getSelection( fallbackSelection );
1006 int fpWithPads = 0;
1007
1008 for( EDA_ITEM* edaItem : selection )
1009 {
1010 if( edaItem->IsBOARD_ITEM() && static_cast<BOARD_ITEM*>( edaItem )->Type() == PCB_FOOTPRINT_T
1011 && !static_cast<FOOTPRINT*>( edaItem )->Pads().empty() )
1012 {
1013 fpWithPads++;
1014 }
1015 }
1016
1017 if( fpWithPads > 0 )
1018 {
1019 // A modal dialog must not be shown from this synchronous grid handler.
1020 // Veto the edit and re-drive it from the main loop once confirmed.
1021 aEvent.Veto();
1022
1024 return;
1025
1026 m_scaleConfirmPending = true;
1027
1028 wxString msg = wxString::Format( _( "%d footprint(s) in the selection have pads. Scaling changes "
1029 "the drawn pad positions but not the physical part. Continue?" ),
1030 fpWithPads );
1031
1032 CallAfter(
1033 [this, msg, propName, newValue]()
1034 {
1035 if( wxMessageBox( msg, _( "Scale footprint with pads?" ), wxYES_NO | wxICON_WARNING, this )
1036 == wxYES )
1037 {
1038 applyConfirmedScale( propName, newValue );
1039 }
1040
1041 m_scaleConfirmPending = false;
1042 } );
1043
1044 return;
1045 }
1046 }
1047 }
1048
1049 aEvent.Skip();
1050}
1051
1052
1053void PCB_PROPERTIES_PANEL::applyConfirmedScale( const wxString& aPropName, const wxVariant& aValue )
1054{
1055 wxPGProperty* pgProp = m_grid->GetPropertyByName( aPropName );
1056
1057 if( !pgProp )
1058 return;
1059
1060 // Re-drive the normal changed path now that we are back in the main loop.
1061 wxPropertyGridEvent evt( wxEVT_PG_CHANGED );
1062 evt.SetEventObject( m_grid );
1063 evt.SetProperty( pgProp );
1064 evt.SetPropertyValue( aValue );
1065
1066 valueChanged( evt );
1067}
1068
1069
1070void PCB_PROPERTIES_PANEL::valueChanged( wxPropertyGridEvent& aEvent )
1071{
1073 return;
1074
1075 SELECTION fallbackSelection;
1076 SELECTION rawSelection = getSelection( fallbackSelection );
1077
1078 // Strip read-only generator children, last ditch sanity check
1079 SELECTION filtered = filterOutReadOnlyGenChildren( rawSelection );
1080
1081 if( filtered.Empty() )
1082 {
1083 aEvent.Veto();
1084 return;
1085 }
1086
1087 const SELECTION& selection = filtered;
1088
1089 wxCHECK( getPropertyFromEvent( aEvent ), /* void */ );
1090
1091 wxVariant newValue = aEvent.GetPropertyValue();
1092 BOARD_COMMIT changes( m_frame );
1093
1094 PROPERTY_COMMIT_HANDLER handler( &changes );
1095
1096 // Multi-footprint scale: rescale each footprint around the selection's
1097 // geometric (bbox) center instead of around its own anchor. Single-fp
1098 // edits go through the regular setter path (anchored at the fp itself).
1099 const wxString propName = aEvent.GetPropertyName();
1100 const bool isScaleX = ( propName == _HKI( "Scale X" ) );
1101 const bool isScaleY = ( propName == _HKI( "Scale Y" ) );
1102 int fpInSelection = 0;
1103 BOX2I selectionBBox;
1104
1105 if( isScaleX || isScaleY )
1106 {
1107 for( EDA_ITEM* edaItem : selection )
1108 {
1109 if( edaItem->IsBOARD_ITEM() && static_cast<BOARD_ITEM*>( edaItem )->Type() == PCB_FOOTPRINT_T )
1110 {
1111 fpInSelection++;
1112 selectionBBox.Merge( static_cast<FOOTPRINT*>( edaItem )->GetBoundingBox() );
1113 }
1114 }
1115 }
1116
1117 const bool useSelectionCenter = fpInSelection > 1;
1118 const VECTOR2I selectionCenter = useSelectionCenter ? selectionBBox.GetCenter() : VECTOR2I( 0, 0 );
1119
1120 // Driving length constraints touched by this edit solved again after commit lands
1121 std::vector<PCB_CONSTRAINT*> drivingConstraints;
1122
1123 for( EDA_ITEM* edaItem : selection )
1124 {
1125 if( !edaItem->IsBOARD_ITEM() )
1126 continue;
1127
1128 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( edaItem );
1129 PROPERTY_BASE* property = m_propMgr.GetProperty( item, aEvent.GetPropertyName() );
1130 wxCHECK( property, /* void */ );
1131
1132 if( item->Type() == PCB_TABLECELL_T )
1133 changes.Modify( item->GetParent(), nullptr, RECURSE_MODE::NO_RECURSE );
1134 else if( item->Type() == PCB_GENERATOR_T )
1135 changes.Modify( item, nullptr, RECURSE_MODE::RECURSE );
1136 else
1137 changes.Modify( item, nullptr, RECURSE_MODE::NO_RECURSE );
1138
1139 // In the PCB Editor, we generally restrict pad movement to the footprint (like dragging)
1140 if( item->Type() == PCB_PAD_T && m_frame
1141 && m_frame->IsType( FRAME_PCB_EDITOR )
1142 && !m_frame->GetPcbNewSettings()->m_AllowFreePads
1143 && ( aEvent.GetPropertyName() == _HKI( "Position X" )
1144 || aEvent.GetPropertyName() == _HKI( "Position Y" ) ) )
1145 {
1146 PAD* pad = static_cast<PAD*>( item );
1147 FOOTPRINT* fp = pad->GetParentFootprint();
1148
1149 if( fp )
1150 {
1151 VECTOR2I oldPos = pad->GetPosition();
1152 VECTOR2I newPos = oldPos;
1153
1154 if( aEvent.GetPropertyName() == _HKI( "Position X" ) )
1155 newPos.x = (int) newValue.GetLong();
1156 else
1157 newPos.y = (int) newValue.GetLong();
1158
1159 VECTOR2I delta = newPos - oldPos;
1160
1161 if( delta.x != 0 || delta.y != 0 )
1162 {
1163 changes.Modify( fp );
1164 fp->Move( delta );
1165 }
1166 }
1167
1168 continue;
1169 }
1170
1171 // Handle variant-aware boolean properties for footprints
1172 if( item->Type() == PCB_FOOTPRINT_T )
1173 {
1174 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
1175 wxString variantName;
1176
1177 if( footprint->GetBoard() )
1178 variantName = footprint->GetBoard()->GetCurrentVariant();
1179
1180 if( !variantName.IsEmpty() )
1181 {
1182 if( propName == _HKI( "Do not Populate" )
1183 || propName == _HKI( "Exclude From Bill of Materials" )
1184 || propName == _HKI( "Exclude From Simulation" )
1185 || propName == _HKI( "Exclude From Position Files" ) )
1186 {
1187 FOOTPRINT_VARIANT* variant = footprint->GetVariant( variantName );
1188
1189 if( !variant )
1190 variant = footprint->AddVariant( variantName );
1191
1192 if( variant )
1193 {
1194 bool boolValue = newValue.GetBool();
1195
1196 if( propName == _HKI( "Do not Populate" ) )
1197 variant->SetDNP( boolValue );
1198 else if( propName == _HKI( "Exclude From Bill of Materials" ) )
1199 variant->SetExcludedFromBOM( boolValue );
1200 else if( propName == _HKI( "Exclude From Simulation" ) )
1201 variant->SetExcludedFromSim( boolValue );
1202 else if( propName == _HKI( "Exclude From Position Files" ) )
1203 variant->SetExcludedFromPosFiles( boolValue );
1204
1205 continue;
1206 }
1207 }
1208 }
1209 }
1210
1211 if( useSelectionCenter && item->Type() == PCB_FOOTPRINT_T )
1212 {
1213 FOOTPRINT* fp = static_cast<FOOTPRINT*>( item );
1214 double newScale = newValue.GetDouble();
1215 double relSx = isScaleX ? newScale / fp->GetScaleX() : 1.0;
1216 double relSy = isScaleY ? newScale / fp->GetScaleY() : 1.0;
1217
1218 fp->RescaleAroundPoint( selectionCenter, relSx, relSy );
1219 continue;
1220 }
1221
1222 // Value mode and Driving value live partly in a board level constraint not item scoped
1223 // so route them through this commit for a shared undo step
1224 if( BaseType( item->Type() ) == PCB_DIMENSION_T )
1225 {
1226 PCB_DIMENSION_BASE* dim = static_cast<PCB_DIMENSION_BASE*>( item );
1227 BOARD* board = m_frame->GetBoard();
1228
1229 if( propName == _HKI( "Value Mode" ) )
1230 {
1231 DIM_VALUE_MODE mode = static_cast<DIM_VALUE_MODE>( newValue.GetLong() );
1232
1233 // Seed from current display value so a mode switch alone does not move geometry
1234 std::optional<int> length;
1235
1236 if( mode == DIM_VALUE_MODE::DRIVING )
1237 {
1238 PCB_CONSTRAINT* existing = FindDimensionLengthConstraint( board, dim );
1239
1240 if( existing && existing->GetValue() )
1241 length = KiROUND( *existing->GetValue() );
1242 else
1243 length = dim->GetMeasuredValue();
1244 }
1245
1246 // Arbitrary keeps measured text until edited
1247 std::optional<wxString> overrideText;
1248
1249 if( mode == DIM_VALUE_MODE::ARBITRARY && !dim->GetOverrideTextEnabled() )
1250 overrideText = dim->GetValueText();
1251
1253 board, dim, mode, length, overrideText,
1254 [&]( BOARD_ITEM* aItem ) { changes.Modify( aItem ); },
1255 [&]( BOARD_ITEM* aItem ) { changes.Add( aItem ); },
1256 [&]( BOARD_ITEM* aItem ) { changes.Remove( aItem ); } );
1257
1258 if( driving )
1259 drivingConstraints.push_back( driving );
1260
1261 continue;
1262 }
1263
1264 if( propName == _HKI( "Value" ) && dim->GetValueMode() == DIM_VALUE_MODE::DRIVING )
1265 {
1266 // Parsed in dimension own units to match display validator already blocked
1267 // non positive entries other selected dims failing parse left unchanged
1269 newValue.GetString() );
1270
1271 if( iu > 0.0 )
1272 {
1273 if( PCB_CONSTRAINT* driving = FindDimensionLengthConstraint( board, dim ) )
1274 {
1275 changes.Modify( driving );
1276 driving->SetValue( KiROUND( iu ) );
1277 drivingConstraints.push_back( driving );
1278 }
1279 }
1280
1281 continue;
1282 }
1283 }
1284
1285 item->Set( property, newValue );
1286
1287 // A generated table's cells come from the settings just changed, so they say nothing
1288 // about the edit until the table has been laid out again
1289 if( IsGeneratedTableType( item->Type() ) && item->GetBoard() )
1290 static_cast<PCB_GENERATED_TABLE*>( item )->RebuildCells( *item->GetBoard() );
1291 }
1292
1293 changes.Push( _( "Edit Properties" ) );
1294
1295 // Edit is authoritative so hold shapes fixed while solving neighbors and fold into this
1296 // undo with a no op if nothing was touched
1297 if( CONSTRAINT_EDIT_TOOL* constraintTool = m_frame->GetToolManager()->GetTool<CONSTRAINT_EDIT_TOOL>() )
1298 {
1299 std::vector<PCB_SHAPE*> shapes;
1300 EDIT_TOOL::collectConstraintShapes( selection, shapes );
1301 constraintTool->SolveAfterEdit( shapes );
1302 }
1303
1304 // Driving length now on board so solve bound geometry to it and APPEND_UNDO folds follow
1305 // up moves into one undo
1306 if( !drivingConstraints.empty() )
1307 {
1308 BOARD_COMMIT solveCommit( m_frame );
1309
1310 for( PCB_CONSTRAINT* constraint : drivingConstraints )
1311 {
1312 ApplyConstraintImmediately( m_frame->GetBoard(), constraint, nullptr,
1313 [&]( BOARD_ITEM* aItem )
1314 {
1315 solveCommit.Modify( aItem );
1316 } );
1317 }
1318
1319 if( !solveCommit.Empty() )
1320 solveCommit.Push( _( "Apply Dimension Length" ), APPEND_UNDO );
1321 }
1322
1323 m_frame->Refresh();
1324
1325 // Perform grid updates as necessary based on value change
1326 AfterCommit();
1327
1328 // PointEditor may need to update if locked/unlocked
1329 if( aEvent.GetPropertyName() == _HKI( "Locked" ) )
1330 m_frame->GetToolManager()->ProcessEvent( EVENTS::SelectedEvent );
1331
1332 aEvent.Skip();
1333}
1334
1335
1337{
1338 wxPGChoices layersAll;
1339 wxPGChoices layersCu;
1340 wxPGChoices nets;
1341 wxPGChoices fonts;
1342
1343 // Regenerate all layers
1344 for( PCB_LAYER_ID layer : aBoard->GetEnabledLayers().UIOrder() )
1345 layersAll.Add( LSET::Name( layer ), layer );
1346
1347 for( PCB_LAYER_ID layer : LSET( aBoard->GetEnabledLayers() & LSET::AllCuMask() ).UIOrder() )
1348 layersCu.Add( LSET::Name( layer ), layer );
1349
1350 m_propMgr.GetProperty( TYPE_HASH( BOARD_ITEM ), _HKI( "Layer" ) )->SetChoices( layersAll );
1351 m_propMgr.GetProperty( TYPE_HASH( PCB_SHAPE ), _HKI( "Layer" ) )->SetChoices( layersAll );
1352
1353 // Copper only properties
1354 m_propMgr.GetProperty( TYPE_HASH( BOARD_CONNECTED_ITEM ), _HKI( "Layer" ) )->SetChoices( layersCu );
1355 m_propMgr.GetProperty( TYPE_HASH( PAD ), _HKI( "Bottom Backdrill Must-Cut" ) )->SetChoices( layersCu );
1356 m_propMgr.GetProperty( TYPE_HASH( PAD ), _HKI( "Top Backdrill Must-Cut" ) )->SetChoices( layersCu );
1357 m_propMgr.GetProperty( TYPE_HASH( PCB_VIA ), _HKI( "Layer Top" ) )->SetChoices( layersCu );
1358 m_propMgr.GetProperty( TYPE_HASH( PCB_VIA ), _HKI( "Layer Bottom" ) )->SetChoices( layersCu );
1359 m_propMgr.GetProperty( TYPE_HASH( PCB_VIA ), _HKI( "Bottom Backdrill Must-Cut" ) )->SetChoices( layersCu );
1360 m_propMgr.GetProperty( TYPE_HASH( PCB_VIA ), _HKI( "Top Backdrill Must-Cut" ) )->SetChoices( layersCu );
1361 m_propMgr.GetProperty( TYPE_HASH( PCB_TUNING_PATTERN ), _HKI( "Layer" ) )->SetChoices( layersCu );
1362
1363 // Regenerate nets
1364
1365 std::vector<std::pair<wxString, int>> netNames;
1366 netNames.reserve( aBoard->GetNetInfo().NetsByNetcode().size() );
1367
1368 for( const auto& [ netCode, netInfo ] : aBoard->GetNetInfo().NetsByNetcode() )
1369 netNames.emplace_back( UnescapeString( netInfo->GetNetname() ), netCode );
1370
1371 std::sort( netNames.begin(), netNames.end(),
1372 []( const auto& a, const auto& b )
1373 {
1374 return a.first.CmpNoCase( b.first ) < 0;
1375 } );
1376
1377 for( const auto& [ netName, netCode ] : netNames )
1378 nets.Add( netName, netCode );
1379
1380 auto netProperty = m_propMgr.GetProperty( TYPE_HASH( BOARD_CONNECTED_ITEM ), _HKI( "Net" ) );
1381 netProperty->SetChoices( nets );
1382
1383 auto tuningNet = m_propMgr.GetProperty( TYPE_HASH( PCB_TUNING_PATTERN ), _HKI( "Net" ) );
1384 tuningNet->SetChoices( nets );
1385
1386 auto stitchNet = m_propMgr.GetProperty( TYPE_HASH( PCB_VIA_STITCH ), _HKI( "Net" ) );
1387 stitchNet->SetChoices( nets );
1388
1389 auto stitchGuardedNet = m_propMgr.GetProperty( TYPE_HASH( PCB_VIA_STITCH ), _HKI( "Guarded Net" ) );
1390 stitchGuardedNet->SetChoices( nets );
1391
1392 // Drill spans come from the stackup rather than from an enum, and enumerating them costs a
1393 // walk of the board, so only a board that actually has a map pays for it
1394 if( aBoard->DrillSymbolLayers().any() )
1395 {
1396 wxPGChoices spans;
1397 spans.Add( _( "All spans" ), -1 );
1398
1399 int index = 0;
1400
1401 for( const DRILL_SPAN& span : EnumerateDrillSpans( *aBoard ) )
1402 {
1403 spans.Add( wxString::Format( wxT( "%s - %s%s" ),
1404 aBoard->GetLayerName( span.TopLayer() ),
1405 aBoard->GetLayerName( span.BottomLayer() ),
1406 span.m_IsBackdrill ? _( " (backdrill)" )
1407 : wxString( wxEmptyString ) ),
1408 index++ );
1409 }
1410
1411 m_propMgr.GetProperty( TYPE_HASH( PCB_DRILL_MAP ), _HKI( "Hole Span" ) )
1412 ->SetChoices( spans );
1413 }
1414}
1415
1416
1417bool PCB_PROPERTIES_PANEL::getItemValue( EDA_ITEM* aItem, PROPERTY_BASE* aProperty, wxVariant& aValue )
1418{
1419 // For FOOTPRINT variant-aware boolean properties, return variant-specific values
1420 if( aItem->Type() == PCB_FOOTPRINT_T )
1421 {
1422 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem );
1423 const wxString& propName = aProperty->Name();
1424 wxString variantName;
1425
1426 if( footprint->GetBoard() )
1427 variantName = footprint->GetBoard()->GetCurrentVariant();
1428
1429 if( propName == _HKI( "Do not Populate" ) )
1430 {
1431 aValue = wxVariant( footprint->GetDNPForVariant( variantName ) );
1432 return true;
1433 }
1434 else if( propName == _HKI( "Exclude From Bill of Materials" ) )
1435 {
1436 aValue = wxVariant( footprint->GetExcludedFromBOMForVariant( variantName ) );
1437 return true;
1438 }
1439 else if( propName == _HKI( "Exclude From Simulation" ) )
1440 {
1441 aValue = wxVariant( footprint->GetExcludedFromSimForVariant( variantName ) );
1442 return true;
1443 }
1444 else if( propName == _HKI( "Exclude From Position Files" ) )
1445 {
1446 aValue = wxVariant( footprint->GetExcludedFromPosFilesForVariant( variantName ) );
1447 return true;
1448 }
1449 }
1450
1451 return PROPERTIES_PANEL::getItemValue( aItem, aProperty, aValue );
1452}
int index
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
CONSTRAINT_DIAGNOSIS ApplyConstraintImmediately(BOARD *aBoard, const PCB_CONSTRAINT *aConstraint, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify, const std::set< KIID > &aFixedShapes)
Solve a just-created constraint's cluster so the geometry snaps to satisfy it (SolidWorks-style),...
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:266
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1215
const LSET & DrillSymbolLayers() const
Layers that currently have a drill map on them.
Definition board.h:612
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:943
wxString GetCurrentVariant() const
Definition board.h:529
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1185
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr const Vec GetCenter() const
Definition box2.h:227
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:86
bool Empty() const
Definition commit.h:145
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
Interactive authoring of geometric constraints (issue #2329).
A set of EDA_ITEMs (i.e., without duplicates).
Definition eda_group.h:43
virtual EDA_ITEM * AsEdaItem()=0
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
bool GetCustomProperty(const wxString &aKey, wxString &aValue) const
Definition eda_item.cpp:188
void RemoveCustomProperty(const wxString &aKey)
Definition eda_item.cpp:161
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
void SetCustomProperty(const wxString &aKey, const wxString &aValue)
Definition eda_item.h:255
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:347
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:236
static const TOOL_EVENT SelectedEvent
Definition actions.h:345
Variant information for a footprint.
Definition footprint.h:228
void SetExcludedFromPosFiles(bool aExclude)
Definition footprint.h:252
void SetExcludedFromSim(bool aExclude)
Definition footprint.h:249
void SetDNP(bool aDNP)
Definition footprint.h:243
void SetExcludedFromBOM(bool aExclude)
Definition footprint.h:246
void Remove(BOARD_ITEM *aItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
bool GetExcludedFromSimForVariant(const wxString &aVariantName) const
Get the exclude-from-simulation status for a specific variant.
const FOOTPRINT_VARIANT * GetVariant(const wxString &aVariantName) const
Get a variant by name.
bool HasField(const wxString &aFieldName) const
PCB_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this footprint.
std::deque< PAD * > & Pads()
Definition footprint.h:405
double GetScaleX() const
Definition footprint.h:456
void Move(const VECTOR2I &aMoveVector) override
Move this object.
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Adds an item to the container.
void RescaleAroundPoint(const VECTOR2I &aCenter, double aSx, double aSy)
bool GetDNPForVariant(const wxString &aVariantName) const
Get the DNP status for a specific variant.
bool GetExcludedFromPosFilesForVariant(const wxString &aVariantName) const
Get the exclude-from-position-files status for a specific variant.
FOOTPRINT_VARIANT * AddVariant(const wxString &aVariantName)
Add a new variant with the given name.
bool GetExcludedFromBOMForVariant(const wxString &aVariantName) const
Get the exclude-from-BOM status for a specific variant.
double GetScaleY() const
Definition footprint.h:457
bool Set(PROPERTY_BASE *aProperty, wxAny &aValue, bool aNotify=true)
bool IsBOARD_ITEM() const
Definition view_item.h:98
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
LSEQ UIOrder() const
Return the copper, technical and user layers in the order shown in layer widget.
Definition lset.cpp:739
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
static wxString Name(PCB_LAYER_ID aLayerId)
Return the fixed name association with aLayerId.
Definition lset.cpp:184
const NETCODES_MAP & NetsByNetcode() const
Return the netcode map, at least for python.
Definition netinfo.h:261
Definition pad.h:61
Common, abstract interface for edit frames.
A geometric constraint between board items (issue #2329).
std::optional< double > GetValue() const
Abstract dimension API.
EDA_UNITS GetUnits() const
DIM_VALUE_MODE GetValueMode() const
Value mode from board state via DimensionValueMode.
int GetMeasuredValue() const
wxString GetValueText() const
bool GetOverrideTextEnabled() const
Turns on drill symbols at the holes, for one layer.
bool IsMandatory() const
void SetName(const wxString &aName)
Definition pcb_field.h:121
bool IsPrivate() const
Definition pcb_field.h:80
A table whose cell text is derived from the board and rebuilt whenever it goes stale.
virtual bool ChildrenAreReadOnly() const
PCB_BASE_EDIT_FRAME * m_frame
PG_NET_SELECTOR_EDITOR * m_netSelectorEditorInstance
void onNewItemLeftBlank(const wxString &aKey) override
void valueChanged(wxPropertyGridEvent &aEvent) override
PG_UNIT_EDITOR * m_unitEditorInstance
PG_RATIO_EDITOR * m_ratioEditorInstance
wxPGProperty * createPGProperty(const PROPERTY_BASE *aProperty) const override
static SELECTION filterOutReadOnlyGenChildren(const SELECTION &aSelection)
Creates a new selection with any generator children removed that are part of a read only generator.
void rebuildProperties(const SELECTION &aSelection) override
Generates the property grid for a given selection of items.
PCB_PROPERTIES_PANEL(wxWindow *aParent, PCB_BASE_EDIT_FRAME *aFrame)
const SELECTION & getSelection(SELECTION &aFallbackSelection)
Get the current selection from the selection tool.
EDA_ITEM * getFrontItem()
Get the front item of the current selection.
void removeCustomProperty(const wxString &aName)
PROPERTY_BASE * getPropertyFromEvent(const wxPropertyGridEvent &aEvent) const
void removeField(const wxString &aName)
bool buildContextMenu(wxMenu &aMenu, wxPGProperty *aPGProp) override
bool isKeyEditable(const wxPGProperty *aPGProp) const override
PROPERTY_MANAGER & m_propMgr
PG_URL_EDITOR * m_urlEditorInstance
PG_CHECKBOX_EDITOR * m_checkboxEditorInstance
void applyConfirmedScale(const wxString &aPropName, const wxVariant &aValue)
void updateLists(const BOARD *aBoard)
Regenerates caches storing layer and net names.
void onContextMenu(wxCommandEvent &aEvent)
void valueChanging(wxPropertyGridEvent &aEvent) override
bool getItemValue(EDA_ITEM *aItem, PROPERTY_BASE *aProperty, wxVariant &aValue) override
Utility to fetch a property value and convert to wxVariant Precondition: aItem is known to have prope...
PG_TRACK_WIDTH_EDITOR * m_trackWidthEditorInstance
void onKeyRenamed(const wxString &aOldName, const wxString &aNewName) override
PG_FPID_EDITOR * m_fpEditorInstance
bool isKeyNameInUse(const wxString &aName) const override
The selection tool: currently supports:
PCB_SELECTION & GetSelection()
A wxEnumProperty that displays a color next to the enum value.
static const wxString EDITOR_NAME
Definition pg_editors.h:75
static wxString BuildEditorName(EDA_DRAW_FRAME *aFrame)
wxPGWindowList CreateControls(wxPropertyGrid *aGrid, wxPGProperty *aProperty, const wxPoint &aPos, const wxSize &aSize) const override
bool OnEvent(wxPropertyGrid *aGrid, wxPGProperty *aProperty, wxWindow *aWindow, wxEvent &aEvent) const override
void UpdateControl(wxPGProperty *aProperty, wxWindow *aCtrl) const override
wxString GetName() const override
bool GetValueFromControl(wxVariant &aVariant, wxPGProperty *aProperty, wxWindow *aCtrl) const override
static const wxString EDITOR_NAME
PG_NET_SELECTOR_EDITOR()=default
static const wxString EDITOR_NAME
Definition pg_editors.h:117
std::unique_ptr< PROPERTY_EDITOR_UNIT_BINDER > m_unitBinder
void UpdateControl(wxPGProperty *aProperty, wxWindow *aCtrl) const override
void UpdateFrame(PCB_BASE_EDIT_FRAME *aFrame)
wxString GetName() const override
PG_TRACK_WIDTH_EDITOR(PCB_BASE_EDIT_FRAME *aFrame)
static const wxString EDITOR_NAME
PCB_BASE_EDIT_FRAME * m_frame
void setTrackWidthOptions(wxComboBox *aEditor) const
wxPGWindowList CreateControls(wxPropertyGrid *aGrid, wxPGProperty *aProperty, const wxPoint &aPos, const wxSize &aSize) const override
bool GetValueFromControl(wxVariant &aVariant, wxPGProperty *aProperty, wxWindow *aCtrl) const override
static wxString BuildEditorName(PCB_BASE_EDIT_FRAME *aFrame)
bool OnEvent(wxPropertyGrid *aGrid, wxPGProperty *aProperty, wxWindow *aWindow, wxEvent &aEvent) const override
static wxString BuildEditorName(EDA_DRAW_FRAME *aFrame)
static wxString BuildEditorName(EDA_DRAW_FRAME *aFrame)
wxString m_contextMenuPropertyName
PROPERTIES_PANEL(wxWindow *aParent, EDA_BASE_FRAME *aFrame)
wxPropertyGrid * m_grid
void beginLabelEdit(const wxString &aKey, bool aStartBlank=false)
void settlePendingLabelEdit()
Synchronously ends any in-progress label edit; potentially canceling a newly added row.
virtual bool getItemValue(EDA_ITEM *aItem, PROPERTY_BASE *aProperty, wxVariant &aValue)
Utility to fetch a property value and convert to wxVariant Precondition: aItem is known to have prope...
virtual void rebuildProperties(const SELECTION &aSelection)
Generates the property grid for a given selection of items.
void addCategoryButton(const wxString &aGroupKey, const wxString &aTooltip, BITMAPS aBitmap, std::function< void()> aAction, std::function< bool()> aEnableFunc=nullptr, bool aForceCategory=false)
Registers an overlay button on a category caption row.
wxString m_pendingNewKey
Key of a freshly-added blank field/custom property awaiting a name from the user.
virtual size_t TypeHash() const =0
Return type-id of the property type.
wxString Group() const
Definition property.h:365
virtual bool HasChoices() const
Return true if this PROPERTY has a limited set of possible values.
Definition property.h:247
const wxString & Name() const
Definition property.h:221
virtual const wxPGChoices & Choices() const
Return a limited set of possible values (e.g.
Definition property.h:227
virtual size_t OwnerHash() const =0
Return type-id of the Owner class.
Provide class metadata.
virtual void Add(EDA_ITEM *aItem)
A null aItem is ignored; the selection never holds null members.
Definition selection.cpp:38
void SetIsHover(bool aIsHover)
Definition selection.h:80
bool IsHover() const
Definition selection.h:85
EDA_ITEM * Front() const
Definition selection.h:176
virtual void Clear() override
Remove all the stored items from the group.
Definition selection.h:97
bool Empty() const
Checks if there is anything selected.
Definition selection.h:114
PCB_CONSTRAINT * FindDimensionLengthConstraint(BOARD *aBoard, const PCB_DIMENSION_BASE *aDimension)
Self FIXED_LENGTH constraint whose members are exactly aDimension START and END or nullptr the drivin...
PCB_CONSTRAINT * SetDimensionValueMode(BOARD *aBoard, PCB_DIMENSION_BASE *aDimension, DIM_VALUE_MODE aMode, std::optional< int > aDrivingLengthIU, const std::optional< wxString > &aOverrideText, const std::function< void(BOARD_ITEM *)> &aBeforeModify, const std::function< void(BOARD_ITEM *)> &aStageAdd, const std::function< void(BOARD_ITEM *)> &aBeforeRemove)
Apply a value mode transition to aDimension Driving creates or updates the driving length with aDrivi...
DIM_VALUE_MODE
Mode a value bearing dimension value is in Driven mirrors measured geometry Driving forces geometry t...
std::vector< DRILL_SPAN > EnumerateDrillSpans(const BOARD &aBoard, bool aPadBackdrills)
Every drill span present on the board, through-holes first.
#define _(s)
@ RECURSE
Definition eda_item.h:51
@ NO_RECURSE
Definition eda_item.h:52
@ FRAME_PCB_EDITOR
Definition frame_type.h:38
@ FRAME_FOOTPRINT_EDITOR
Definition frame_type.h:39
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition lset.cpp:750
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.
KICOMMON_API wxString GetLabel(EDA_UNITS aUnits, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
Get the units string for a given units type.
#define _HKI(x)
Definition page_info.cpp:40
wxPGProperty * PGPropertyFactory(const PROPERTY_BASE *aProperty, EDA_DRAW_FRAME *aFrame)
see class PGM_BASE
APIIMPORT wxPGGlobalVarsClass * wxPGGlobalVars
@ ID_CTX_REMOVE_CUSTOM_PROPERTY
@ ID_CTX_REMOVE_FIELD
@ ID_CTX_ADD_CUSTOM_PROPERTY
@ ID_CTX_ADD_FIELD
#define TYPE_HASH(x)
Macro to generate unique identifier for a type.
Definition property.h:74
std::optional< std::unique_ptr< VALIDATION_ERROR > > VALIDATOR_RESULT
Null optional means validation succeeded.
#define APPEND_UNDO
Definition sch_commit.h:39
std::vector< FAB_LAYER_COLOR > dummy
wxString UnescapeString(const wxString &aSource)
wxString GetUserFieldName(int aFieldNdx, TRANSLATION aTranslation)
wxString GetDefaultFieldName(FIELD_T aFieldId, TRANSLATION aTranslation)
Return a default symbol field name for a mandatory field type.
@ USER
The field ID hasn't been set yet; field is invalid.
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ DATASHEET
name of datasheet
@ UNTRANSLATED
wxString result
Test unit parsing edge cases and error handling.
int delta
constexpr KICAD_T BaseType(const KICAD_T aType)
Return the underlying type of the given type.
Definition typeinfo.h:259
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:83
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:87
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:78
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_DIMENSION_T
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition typeinfo.h:92
constexpr bool IsGeneratedTableType(const KICAD_T aType)
A PCB_TABLE subclass whose cells are generated from the board rather than typed by the user.
Definition typeinfo.h:325
Functions to provide common constants and other functions to assist in making a consistent UI.
#define INDETERMINATE_STATE
Used for holding indeterminate values, such as with multiple selections holding different values or c...
Definition ui_common.h:46
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708