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: break;
678 }
679}
680
681
683{
684 SELECTION fallbackSelection;
685 SELECTION editableSelection = filterOutReadOnlyGenChildren( getSelection( fallbackSelection ) );
686
688
689 if( editableSelection.Empty() )
690 return;
691
692 // Pick a unique untranslated placeholder name that doesn't collide with an existing field.
693 wxString name;
694
695 for( int n = 0; ; ++n )
696 {
698 bool used = false;
699
700 for( EDA_ITEM* item : editableSelection )
701 {
702 if( item->Type() == PCB_FOOTPRINT_T && static_cast<FOOTPRINT*>( item )->HasField( name ) )
703 {
704 used = true;
705 break;
706 }
707 }
708
709 if( !used )
710 break;
711 }
712
713 BOARD_COMMIT changes( m_frame );
714 PROPERTY_COMMIT_HANDLER handler( &changes );
715
716 for( EDA_ITEM* item : editableSelection )
717 {
718 if( item->Type() != PCB_FOOTPRINT_T )
719 continue;
720
721 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
722 PCB_FIELD* field = new PCB_FIELD( footprint, FIELD_T::USER, name );
723
724 field->SetText( wxEmptyString );
725 field->SetVisible( false );
726 changes.Modify( footprint, nullptr, RECURSE_MODE::NO_RECURSE );
727 footprint->Add( field );
728 }
729
730 changes.Push( _( "Add Field" ) );
731 AfterCommit();
732
734
735 beginLabelEdit( name, true );
736}
737
738
740{
741 SELECTION fallbackSelection;
742 SELECTION editableSelection = filterOutReadOnlyGenChildren( getSelection( fallbackSelection ) );
743
744
746
747 if( editableSelection.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 : editableSelection )
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 : editableSelection )
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 SELECTION editableSelection = filterOutReadOnlyGenChildren( getSelection( fallbackSelection ) );
797
798 BOARD_COMMIT changes( m_frame );
799 PROPERTY_COMMIT_HANDLER handler( &changes );
800
801 for( EDA_ITEM* item : editableSelection )
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 SELECTION editableSelection = filterOutReadOnlyGenChildren( getSelection( fallbackSelection ) );
827
828 BOARD_COMMIT changes( m_frame );
829 PROPERTY_COMMIT_HANDLER handler( &changes );
830
831 for( EDA_ITEM* item : editableSelection )
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 m_ValueChangeVetoed = true;
984 return;
985 }
986
987 // Scaling a footprint that has pads is dangerous (the physical
988 // part keeps its original size), so confirm before applying.
989 const wxString propName = aEvent.GetPropertyName();
990
991 if( propName == _HKI( "Scale X" ) || propName == _HKI( "Scale Y" ) )
992 {
993 const double newScale = newValue.GetDouble();
994
995 // Zero, negative, and non-finite scales make the footprint transform degenerate.
996 if( !std::isfinite( newScale ) || newScale <= 0.0 )
997 {
998 m_frame->ShowInfoBarError( _( "Scale must be a positive number." ) );
999 aEvent.Veto();
1000 return;
1001 }
1002
1003 if( newScale != 1.0 )
1004 {
1005 SELECTION fallbackSelection;
1006 const SELECTION& selection = getSelection( fallbackSelection );
1007 int fpWithPads = 0;
1008
1009 for( EDA_ITEM* edaItem : selection )
1010 {
1011 if( edaItem->IsBOARD_ITEM() && static_cast<BOARD_ITEM*>( edaItem )->Type() == PCB_FOOTPRINT_T
1012 && !static_cast<FOOTPRINT*>( edaItem )->Pads().empty() )
1013 {
1014 fpWithPads++;
1015 }
1016 }
1017
1018 if( fpWithPads > 0 )
1019 {
1020 // A modal dialog must not be shown from this synchronous grid handler.
1021 // Veto the edit and re-drive it from the main loop once confirmed.
1022 aEvent.Veto();
1023
1025 return;
1026
1027 m_scaleConfirmPending = true;
1028
1029 wxString msg = wxString::Format( _( "%d footprint(s) in the selection have pads. Scaling changes "
1030 "the drawn pad positions but not the physical part. Continue?" ),
1031 fpWithPads );
1032
1033 CallAfter(
1034 [this, msg, propName, newValue]()
1035 {
1036 if( wxMessageBox( msg, _( "Scale footprint with pads?" ), wxYES_NO | wxICON_WARNING, this )
1037 == wxYES )
1038 {
1039 applyConfirmedScale( propName, newValue );
1040 }
1041
1042 m_scaleConfirmPending = false;
1043 } );
1044
1045 return;
1046 }
1047 }
1048 }
1049
1050 aEvent.Skip();
1051}
1052
1053
1054void PCB_PROPERTIES_PANEL::applyConfirmedScale( const wxString& aPropName, const wxVariant& aValue )
1055{
1056 wxPGProperty* pgProp = m_grid->GetPropertyByName( aPropName );
1057
1058 if( !pgProp )
1059 return;
1060
1061 // Re-drive the normal changed path now that we are back in the main loop.
1062 wxPropertyGridEvent evt( wxEVT_PG_CHANGED );
1063 evt.SetEventObject( m_grid );
1064 evt.SetProperty( pgProp );
1065 evt.SetPropertyValue( aValue );
1066
1067 valueChanged( evt );
1068}
1069
1070
1071void PCB_PROPERTIES_PANEL::valueChanged( wxPropertyGridEvent& aEvent )
1072{
1074 return;
1075
1076 SELECTION fallbackSelection;
1077 SELECTION rawSelection = getSelection( fallbackSelection );
1078
1079 // Strip read-only generator children, last ditch sanity check
1080 SELECTION filtered = filterOutReadOnlyGenChildren( rawSelection );
1081
1082 if( filtered.Empty() )
1083 {
1084 aEvent.Veto();
1085 return;
1086 }
1087
1088 const SELECTION& selection = filtered;
1089
1090 wxCHECK( getPropertyFromEvent( aEvent ), /* void */ );
1091
1092 wxVariant newValue = aEvent.GetPropertyValue();
1093 BOARD_COMMIT changes( m_frame );
1094
1095 PROPERTY_COMMIT_HANDLER handler( &changes );
1096
1097 // Multi-footprint scale: rescale each footprint around the selection's
1098 // geometric (bbox) center instead of around its own anchor. Single-fp
1099 // edits go through the regular setter path (anchored at the fp itself).
1100 const wxString propName = aEvent.GetPropertyName();
1101 const bool isScaleX = ( propName == _HKI( "Scale X" ) );
1102 const bool isScaleY = ( propName == _HKI( "Scale Y" ) );
1103 int fpInSelection = 0;
1104 BOX2I selectionBBox;
1105
1106 if( isScaleX || isScaleY )
1107 {
1108 for( EDA_ITEM* edaItem : selection )
1109 {
1110 if( edaItem->IsBOARD_ITEM() && static_cast<BOARD_ITEM*>( edaItem )->Type() == PCB_FOOTPRINT_T )
1111 {
1112 fpInSelection++;
1113 selectionBBox.Merge( static_cast<FOOTPRINT*>( edaItem )->GetBoundingBox() );
1114 }
1115 }
1116 }
1117
1118 const bool useSelectionCenter = fpInSelection > 1;
1119 const VECTOR2I selectionCenter = useSelectionCenter ? selectionBBox.GetCenter() : VECTOR2I( 0, 0 );
1120
1121 // Driving length constraints touched by this edit solved again after commit lands
1122 std::vector<PCB_CONSTRAINT*> drivingConstraints;
1123
1124 for( EDA_ITEM* edaItem : selection )
1125 {
1126 if( !edaItem->IsBOARD_ITEM() )
1127 continue;
1128
1129 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( edaItem );
1130 PROPERTY_BASE* property = m_propMgr.GetProperty( item, aEvent.GetPropertyName() );
1131 wxCHECK( property, /* void */ );
1132
1133 if( item->Type() == PCB_TABLECELL_T )
1134 changes.Modify( item->GetParent(), nullptr, RECURSE_MODE::NO_RECURSE );
1135 else if( item->Type() == PCB_GENERATOR_T )
1136 changes.Modify( item, nullptr, RECURSE_MODE::RECURSE );
1137 else
1138 changes.Modify( item, nullptr, RECURSE_MODE::NO_RECURSE );
1139
1140 // In the PCB Editor, we generally restrict pad movement to the footprint (like dragging)
1141 if( item->Type() == PCB_PAD_T && m_frame
1142 && m_frame->IsType( FRAME_PCB_EDITOR )
1143 && !m_frame->GetPcbNewSettings()->m_AllowFreePads
1144 && ( aEvent.GetPropertyName() == _HKI( "Position X" )
1145 || aEvent.GetPropertyName() == _HKI( "Position Y" ) ) )
1146 {
1147 PAD* pad = static_cast<PAD*>( item );
1148 FOOTPRINT* fp = pad->GetParentFootprint();
1149
1150 if( fp )
1151 {
1152 VECTOR2I oldPos = pad->GetPosition();
1153 VECTOR2I newPos = oldPos;
1154
1155 if( aEvent.GetPropertyName() == _HKI( "Position X" ) )
1156 newPos.x = (int) newValue.GetLong();
1157 else
1158 newPos.y = (int) newValue.GetLong();
1159
1160 VECTOR2I delta = newPos - oldPos;
1161
1162 if( delta.x != 0 || delta.y != 0 )
1163 {
1164 changes.Modify( fp );
1165 fp->Move( delta );
1166 }
1167 }
1168
1169 continue;
1170 }
1171
1172 // Handle variant-aware boolean properties for footprints
1173 if( item->Type() == PCB_FOOTPRINT_T )
1174 {
1175 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
1176 wxString variantName;
1177
1178 if( footprint->GetBoard() )
1179 variantName = footprint->GetBoard()->GetCurrentVariant();
1180
1181 if( !variantName.IsEmpty() )
1182 {
1183 if( propName == _HKI( "Do not Populate" )
1184 || propName == _HKI( "Exclude From Bill of Materials" )
1185 || propName == _HKI( "Exclude From Simulation" )
1186 || propName == _HKI( "Exclude From Position Files" ) )
1187 {
1188 FOOTPRINT_VARIANT* variant = footprint->GetVariant( variantName );
1189
1190 if( !variant )
1191 variant = footprint->AddVariant( variantName );
1192
1193 if( variant )
1194 {
1195 bool boolValue = newValue.GetBool();
1196
1197 if( propName == _HKI( "Do not Populate" ) )
1198 variant->SetDNP( boolValue );
1199 else if( propName == _HKI( "Exclude From Bill of Materials" ) )
1200 variant->SetExcludedFromBOM( boolValue );
1201 else if( propName == _HKI( "Exclude From Simulation" ) )
1202 variant->SetExcludedFromSim( boolValue );
1203 else if( propName == _HKI( "Exclude From Position Files" ) )
1204 variant->SetExcludedFromPosFiles( boolValue );
1205
1206 continue;
1207 }
1208 }
1209 }
1210 }
1211
1212 if( useSelectionCenter && item->Type() == PCB_FOOTPRINT_T )
1213 {
1214 FOOTPRINT* fp = static_cast<FOOTPRINT*>( item );
1215 double newScale = newValue.GetDouble();
1216 double relSx = isScaleX ? newScale / fp->GetScaleX() : 1.0;
1217 double relSy = isScaleY ? newScale / fp->GetScaleY() : 1.0;
1218
1219 fp->RescaleAroundPoint( selectionCenter, relSx, relSy );
1220 continue;
1221 }
1222
1223 // Value mode and Driving value live partly in a board level constraint not item scoped
1224 // so route them through this commit for a shared undo step
1225 if( BaseType( item->Type() ) == PCB_DIMENSION_T )
1226 {
1227 PCB_DIMENSION_BASE* dim = static_cast<PCB_DIMENSION_BASE*>( item );
1228 BOARD* board = m_frame->GetBoard();
1229
1230 if( propName == _HKI( "Value Mode" ) )
1231 {
1232 DIM_VALUE_MODE mode = static_cast<DIM_VALUE_MODE>( newValue.GetLong() );
1233
1234 // Seed from current display value so a mode switch alone does not move geometry
1235 std::optional<int> length;
1236
1237 if( mode == DIM_VALUE_MODE::DRIVING )
1238 {
1239 PCB_CONSTRAINT* existing = FindDimensionLengthConstraint( board, dim );
1240
1241 if( existing && existing->GetValue() )
1242 length = KiROUND( *existing->GetValue() );
1243 else
1244 length = dim->GetMeasuredValue();
1245 }
1246
1247 // Arbitrary keeps measured text until edited
1248 std::optional<wxString> overrideText;
1249
1250 if( mode == DIM_VALUE_MODE::ARBITRARY && !dim->GetOverrideTextEnabled() )
1251 overrideText = dim->GetValueText();
1252
1254 board, dim, mode, length, overrideText,
1255 [&]( BOARD_ITEM* aItem )
1256 {
1257 changes.Modify( aItem );
1258 },
1259 [&]( BOARD_ITEM* aItem )
1260 {
1261 changes.Add( aItem );
1262 },
1263 [&]( BOARD_ITEM* aItem )
1264 {
1265 changes.Remove( aItem );
1266 } );
1267
1268 if( driving )
1269 drivingConstraints.push_back( driving );
1270
1271 continue;
1272 }
1273
1274 if( propName == _HKI( "Value" ) && dim->GetValueMode() == DIM_VALUE_MODE::DRIVING )
1275 {
1276 // Parsed in dimension own units to match display validator already blocked
1277 // non positive entries other selected dims failing parse left unchanged
1279 newValue.GetString() );
1280
1281 if( iu > 0.0 )
1282 {
1283 if( PCB_CONSTRAINT* driving = FindDimensionLengthConstraint( board, dim ) )
1284 {
1285 changes.Modify( driving );
1286 driving->SetValue( KiROUND( iu ) );
1287 drivingConstraints.push_back( driving );
1288 }
1289 }
1290
1291 continue;
1292 }
1293 }
1294
1295 item->Set( property, newValue );
1296
1297 // A generated table's cells come from the settings just changed, so they say nothing
1298 // about the edit until the table has been laid out again
1299 if( IsGeneratedTableType( item->Type() ) && item->GetBoard() )
1300 static_cast<PCB_GENERATED_TABLE*>( item )->RebuildCells( *item->GetBoard() );
1301 }
1302
1303 changes.Push( _( "Edit Properties" ) );
1304
1305 // Edit is authoritative so hold shapes fixed while solving neighbors and fold into this
1306 // undo with a no op if nothing was touched
1307 if( CONSTRAINT_EDIT_TOOL* constraintTool = m_frame->GetToolManager()->GetTool<CONSTRAINT_EDIT_TOOL>() )
1308 {
1309 std::vector<PCB_SHAPE*> shapes;
1310 EDIT_TOOL::collectConstraintShapes( selection, shapes );
1311 constraintTool->SolveAfterEdit( shapes );
1312 }
1313
1314 // Driving length now on board so solve bound geometry to it and APPEND_UNDO folds follow
1315 // up moves into one undo
1316 if( !drivingConstraints.empty() )
1317 {
1318 BOARD_COMMIT solveCommit( m_frame );
1319
1320 for( PCB_CONSTRAINT* constraint : drivingConstraints )
1321 {
1322 ApplyConstraintImmediately( m_frame->GetBoard(), constraint, nullptr,
1323 [&]( BOARD_ITEM* aItem )
1324 {
1325 solveCommit.Modify( aItem );
1326 } );
1327 }
1328
1329 if( !solveCommit.Empty() )
1330 solveCommit.Push( _( "Apply Dimension Length" ), APPEND_UNDO );
1331 }
1332
1333 m_frame->Refresh();
1334
1335 // Perform grid updates as necessary based on value change
1336 AfterCommit();
1337
1338 // PointEditor may need to update if locked/unlocked
1339 if( aEvent.GetPropertyName() == _HKI( "Locked" ) )
1340 m_frame->GetToolManager()->ProcessEvent( EVENTS::SelectedEvent );
1341
1342 aEvent.Skip();
1343}
1344
1345
1347{
1348 wxPGChoices layersAll;
1349 wxPGChoices layersCu;
1350 wxPGChoices nets;
1351 wxPGChoices fonts;
1352
1353 // Regenerate all layers
1354 for( PCB_LAYER_ID layer : aBoard->GetEnabledLayers().UIOrder() )
1355 layersAll.Add( LSET::Name( layer ), layer );
1356
1357 for( PCB_LAYER_ID layer : LSET( aBoard->GetEnabledLayers() & LSET::AllCuMask() ).UIOrder() )
1358 layersCu.Add( LSET::Name( layer ), layer );
1359
1360 m_propMgr.GetProperty( TYPE_HASH( BOARD_ITEM ), _HKI( "Layer" ) )->SetChoices( layersAll );
1361 m_propMgr.GetProperty( TYPE_HASH( PCB_SHAPE ), _HKI( "Layer" ) )->SetChoices( layersAll );
1362
1363 // Copper only properties
1364 m_propMgr.GetProperty( TYPE_HASH( BOARD_CONNECTED_ITEM ), _HKI( "Layer" ) )->SetChoices( layersCu );
1365 m_propMgr.GetProperty( TYPE_HASH( PAD ), _HKI( "Bottom Backdrill Must-Cut" ) )->SetChoices( layersCu );
1366 m_propMgr.GetProperty( TYPE_HASH( PAD ), _HKI( "Top Backdrill Must-Cut" ) )->SetChoices( layersCu );
1367 m_propMgr.GetProperty( TYPE_HASH( PCB_VIA ), _HKI( "Layer Top" ) )->SetChoices( layersCu );
1368 m_propMgr.GetProperty( TYPE_HASH( PCB_VIA ), _HKI( "Layer Bottom" ) )->SetChoices( layersCu );
1369 m_propMgr.GetProperty( TYPE_HASH( PCB_VIA ), _HKI( "Bottom Backdrill Must-Cut" ) )->SetChoices( layersCu );
1370 m_propMgr.GetProperty( TYPE_HASH( PCB_VIA ), _HKI( "Top Backdrill Must-Cut" ) )->SetChoices( layersCu );
1371 m_propMgr.GetProperty( TYPE_HASH( PCB_TUNING_PATTERN ), _HKI( "Layer" ) )->SetChoices( layersCu );
1372
1373 // Regenerate nets
1374
1375 std::vector<std::pair<wxString, int>> netNames;
1376 netNames.reserve( aBoard->GetNetInfo().NetsByNetcode().size() );
1377
1378 for( const auto& [ netCode, netInfo ] : aBoard->GetNetInfo().NetsByNetcode() )
1379 netNames.emplace_back( UnescapeString( netInfo->GetNetname() ), netCode );
1380
1381 std::sort( netNames.begin(), netNames.end(),
1382 []( const auto& a, const auto& b )
1383 {
1384 return a.first.CmpNoCase( b.first ) < 0;
1385 } );
1386
1387 for( const auto& [ netName, netCode ] : netNames )
1388 nets.Add( netName, netCode );
1389
1390 auto netProperty = m_propMgr.GetProperty( TYPE_HASH( BOARD_CONNECTED_ITEM ), _HKI( "Net" ) );
1391 netProperty->SetChoices( nets );
1392
1393 auto tuningNet = m_propMgr.GetProperty( TYPE_HASH( PCB_TUNING_PATTERN ), _HKI( "Net" ) );
1394 tuningNet->SetChoices( nets );
1395
1396 auto stitchNet = m_propMgr.GetProperty( TYPE_HASH( PCB_VIA_STITCH ), _HKI( "Net" ) );
1397 stitchNet->SetChoices( nets );
1398
1399 auto stitchGuardedNet = m_propMgr.GetProperty( TYPE_HASH( PCB_VIA_STITCH ), _HKI( "Guarded Net" ) );
1400 stitchGuardedNet->SetChoices( nets );
1401
1402 // Drill spans come from the stackup rather than from an enum, and enumerating them costs a
1403 // walk of the board, so only a board that actually has a map pays for it
1404 if( aBoard->DrillSymbolLayers().any() )
1405 {
1406 wxPGChoices spans;
1407 spans.Add( _( "All spans" ), -1 );
1408
1409 int index = 0;
1410
1411 for( const DRILL_SPAN& span : EnumerateDrillSpans( *aBoard ) )
1412 {
1413 spans.Add( wxString::Format( wxT( "%s - %s%s" ),
1414 aBoard->GetLayerName( span.TopLayer() ),
1415 aBoard->GetLayerName( span.BottomLayer() ),
1416 span.m_IsBackdrill ? _( " (backdrill)" )
1417 : wxString( wxEmptyString ) ),
1418 index++ );
1419 }
1420
1421 m_propMgr.GetProperty( TYPE_HASH( PCB_DRILL_MAP ), _HKI( "Hole Span" ) )
1422 ->SetChoices( spans );
1423 }
1424}
1425
1426
1427bool PCB_PROPERTIES_PANEL::getItemValue( EDA_ITEM* aItem, PROPERTY_BASE* aProperty, wxVariant& aValue )
1428{
1429 // For FOOTPRINT variant-aware boolean properties, return variant-specific values
1430 if( aItem->Type() == PCB_FOOTPRINT_T )
1431 {
1432 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem );
1433 const wxString& propName = aProperty->Name();
1434 wxString variantName;
1435
1436 if( footprint->GetBoard() )
1437 variantName = footprint->GetBoard()->GetCurrentVariant();
1438
1439 if( propName == _HKI( "Do not Populate" ) )
1440 {
1441 aValue = wxVariant( footprint->GetDNPForVariant( variantName ) );
1442 return true;
1443 }
1444 else if( propName == _HKI( "Exclude From Bill of Materials" ) )
1445 {
1446 aValue = wxVariant( footprint->GetExcludedFromBOMForVariant( variantName ) );
1447 return true;
1448 }
1449 else if( propName == _HKI( "Exclude From Simulation" ) )
1450 {
1451 aValue = wxVariant( footprint->GetExcludedFromSimForVariant( variantName ) );
1452 return true;
1453 }
1454 else if( propName == _HKI( "Exclude From Position Files" ) )
1455 {
1456 aValue = wxVariant( footprint->GetExcludedFromPosFilesForVariant( variantName ) );
1457 return true;
1458 }
1459 }
1460
1461 return PROPERTIES_PANEL::getItemValue( aItem, aProperty, aValue );
1462}
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:85
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:267
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1217
const LSET & DrillSymbolLayers() const
Layers that currently have a drill map on them.
Definition board.h:613
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:932
wxString GetCurrentVariant() const
Definition board.h:530
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1174
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:346
Variant information for a footprint.
Definition footprint.h:178
void SetExcludedFromPosFiles(bool aExclude)
Definition footprint.h:202
void SetExcludedFromSim(bool aExclude)
Definition footprint.h:199
void SetDNP(bool aDNP)
Definition footprint.h:193
void SetExcludedFromBOM(bool aExclude)
Definition footprint.h:196
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:355
double GetScaleX() const
Definition footprint.h:399
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:400
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:377
virtual bool HasChoices() const
Return true if this PROPERTY has a limited set of possible values.
Definition property.h:259
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:239
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(const 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:260
@ 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:326
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:47
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708