KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_zone_properties.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) 2023 Ethan Chien <[email protected]>
5 * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr
6 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
7 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <magic_enum.hpp>
24
25#include <widgets/wx_infobar.h>
27#include <grid_tricks.h>
28#include <wx/radiobut.h>
29#include <kiface_base.h>
30#include <confirm.h>
31#include <pcb_edit_frame.h>
32#include <dialog_shim.h>
33#include <pcbnew_settings.h>
34#include <zone_settings_bag.h>
35#include <wx/string.h>
36#include <widgets/unit_binder.h>
38#include <widgets/wx_grid.h>
39#include <pad.h>
41
42
43wxDEFINE_EVENT( EVT_ZONE_NAME_UPDATE, wxCommandEvent );
44wxDEFINE_EVENT( EVT_ZONE_NET_UPDATE, wxCommandEvent );
45
47 ZONE_SETTINGS_BAG& aZonesSettingsBag, bool allowNetSpec ) :
49 m_frame( aFrame ),
50 m_zonesSettingsBag( aZonesSettingsBag ),
51 m_zone( nullptr ),
62{
63 m_netSelector->SetNetInfo( &m_frame->GetBoard()->GetNetInfo() );
64
65 if( !allowNetSpec )
66 {
67 m_netLabel->Hide();
68 m_netSelector->Hide();
69 }
70
72 [&]() -> LSET
73 {
74 return m_settings->m_Layers;
75 } );
76
79 m_layerSpecificOverrides->SetSelectionMode( wxGrid::wxGridSelectRows );
80
81 wxGridCellAttr* attr = new wxGridCellAttr;
82 attr->SetRenderer( new GRID_CELL_LAYER_RENDERER( nullptr ) );
83 LSET forbiddenLayers = LSET::AllNonCuMask();
84
85 for( PCB_LAYER_ID copper : LSET::AllCuMask() )
86 {
87 if( !m_frame->GetBoard()->IsLayerEnabled( copper ) )
88 forbiddenLayers.set( copper );
89 }
90
91 attr->SetEditor( new GRID_CELL_LAYER_SELECTOR( nullptr, forbiddenLayers ) );
92 m_layerSpecificOverrides->SetColAttr( 0, attr );
93 m_layerSpecificOverrides->SetupColumnAutosizer( 0 );
94
97
98 m_netSelector->Bind( FILTERED_ITEM_SELECTED, &PANEL_ZONE_PROPERTIES::onNetSelector, this );
99}
100
101
103{
104 // we passed ownership of table to grid
105 // delete m_layerPropsTable;
106
107 m_layerSpecificOverrides->PopEventHandler( true );
108
109 m_netSelector->Unbind( FILTERED_ITEM_SELECTED, &PANEL_ZONE_PROPERTIES::onNetSelector, this );
110}
111
112
114{
115 if( m_settings )
117
118 m_zone = aZone;
119 m_settings = m_zonesSettingsBag.GetZoneSettings( aZone );
120
121 if( !m_settings )
122 {
123 m_isTeardrop = false;
124 m_tcZoneName->ChangeValue( wxEmptyString );
125 Enable( false );
126 return;
127 }
128
129 Enable( true );
131
133}
134
135
137{
138 if( !m_settings )
139 return false;
140
141 m_tcZoneName->ChangeValue( m_settings->m_Name );
142
143 if( m_netSelector->IsShown() )
144 m_netSelector->SetSelectedNetcode( std::max( 0, m_settings->m_Netcode ) );
145
146 m_cbLocked->SetValue( m_settings->m_Locked );
147 m_cornerSmoothingChoice->SetSelection( static_cast<int>( m_settings->GetCornerSmoothingType() ) );
148 m_cornerRadius.SetValue( m_settings->GetCornerRadius() );
149
150 if( m_isTeardrop ) // outlines are never smoothed: they have already the right shape
151 {
152 m_cornerSmoothingChoice->SetSelection( 0 );
153 m_cornerSmoothingChoice->Enable( false );
154 m_cornerRadius.Show( false );
155 }
156
157 switch( m_settings->m_ZoneBorderDisplayStyle )
158 {
159 case ZONE_BORDER_DISPLAY_STYLE::NO_HATCH: m_OutlineDisplayCtrl->SetSelection( 0 ); break;
163 }
164
165 m_outlineHatchPitch.SetValue( m_settings->m_BorderHatchPitch );
166
167 m_clearance.SetValue( m_settings->m_ZoneClearance );
168 m_minWidth.SetValue( m_settings->m_ZoneMinThickness );
169
170 switch( m_settings->GetPadConnection() )
171 {
172 default:
173 case ZONE_CONNECTION::THERMAL: m_PadInZoneOpt->SetSelection( 1 ); break;
174 case ZONE_CONNECTION::THT_THERMAL: m_PadInZoneOpt->SetSelection( 2 ); break;
175 case ZONE_CONNECTION::NONE: m_PadInZoneOpt->SetSelection( 3 ); break;
176 case ZONE_CONNECTION::FULL: m_PadInZoneOpt->SetSelection( 0 ); break;
177 }
178
179 if( m_isTeardrop )
180 {
181 m_PadInZoneOpt->SetSelection( 0 );
182 m_PadInZoneOpt->Enable( false );
183 m_antipadClearance.Enable( false );
184 m_spokeWidth.Enable( false );
185 }
186
187 // Do not enable/disable antipad clearance and spoke width. They might be needed if
188 // a footprint or pad overrides the zone to specify a thermal connection.
189 m_antipadClearance.SetValue( m_settings->m_ThermalReliefGap );
190 m_spokeWidth.SetValue( m_settings->m_ThermalReliefSpokeWidth );
191
193 m_islandThreshold.SetDoubleValue( static_cast<double>( m_settings->GetMinIslandArea() ) );
194
195 m_cbRemoveIslands->SetSelection( static_cast<int>( m_settings->GetIslandRemovalMode() ) );
196
198
200 m_gridStyleRotation.SetAngleValue( m_settings->m_HatchOrientation );
201 m_gridStyleThickness.SetValue( m_settings->m_HatchThickness );
202 m_gridStyleGap.SetValue( m_settings->m_HatchGap );
203
204 m_spinCtrlSmoothLevel->SetValue( m_settings->m_HatchSmoothingLevel );
205 m_spinCtrlSmoothValue->SetValue( m_settings->m_HatchSmoothingValue );
206
207 m_layerPropsTable->Clear();
208
209 for( PCB_LAYER_ID layer : LSET::AllCuMask().UIOrder() )
210 {
211 if( m_settings->m_LayerProperties.contains( layer ) )
212 {
213 ZONE_LAYER_PROPERTIES& props = m_settings->m_LayerProperties[layer];
214
215 if( props.hatching_offset.has_value() )
216 m_layerPropsTable->AddItem( layer, props );
217 }
218 }
219
220 // Enable/Disable some widgets
221 wxCommandEvent aEvent;
222 onNetSelector( aEvent );
224 OnRemoveIslandsSelection( aEvent );
225 onHatched( aEvent );
226
227 return true;
228}
229
230
232{
233 m_islandThreshold.Show( m_cbRemoveIslands->GetSelection() == 2 );
234
235 if( DIALOG_SHIM* dlg = dynamic_cast<DIALOG_SHIM*>( wxGetTopLevelParent( this ) ) )
236 dlg->Layout();
237}
238
239
241{
243 magic_enum::enum_cast<ZONE_SETTINGS::CORNER_SMOOTHING>( m_cornerSmoothingChoice->GetSelection() )
245
248 {
249 m_cornerRadius.Show( true );
250 }
251 else
252 {
253 m_cornerRadius.Show( false );
254 }
255
256 if( DIALOG_SHIM* dlg = dynamic_cast<DIALOG_SHIM*>( wxGetTopLevelParent( this ) ) )
257 dlg->Layout();
258}
259
260
261void PANEL_ZONE_PROPERTIES::OnZoneNameChanged( wxCommandEvent& aEvent )
262{
263 if( !m_settings )
264 return;
265
266 // Propagate all the way out so that the MODEL_ZONES_OVERVIEW can pick it up
267 m_settings->m_Name = m_tcZoneName->GetValue();
268
269 if( m_zone )
270 {
271 m_zonesSettingsBag.GetZoneSettings( m_zone )->m_Name = m_settings->m_Name;
272 m_zone->SetZoneName( m_settings->m_Name );
273 }
274
275 wxCommandEvent* evt = new wxCommandEvent( EVT_ZONE_NAME_UPDATE );
276 wxQueueEvent( m_parent, evt );
277}
278
279
280void PANEL_ZONE_PROPERTIES::onNetSelector( wxCommandEvent& aEvent )
281{
282 // There is nothing to do if there is no zone selected which can happen when there are no zones on the board.
283 if( !m_netSelector || !m_netSelector->IsShown() )
284 return;
285
287
288 // Zones with no net never have islands removed
289 if( m_netSelector->GetSelectedNetcode() == INVALID_NET_CODE )
290 {
291 if( m_cbRemoveIslands && m_cbRemoveIslands->IsEnabled() && m_settings )
292 m_settings->SetIslandRemovalMode( (ISLAND_REMOVAL_MODE) m_cbRemoveIslands->GetSelection() );
293
294 m_cbRemoveIslands->SetSelection( 1 );
295 m_removeIslandsLabel->Enable( false );
296 m_cbRemoveIslands->Enable( false );
297 }
298 else if( !m_cbRemoveIslands->IsEnabled() )
299 {
301 m_cbRemoveIslands->SetSelection( static_cast<int>( m_settings->GetIslandRemovalMode() ) );
302
303 m_removeIslandsLabel->Enable( true );
304 m_cbRemoveIslands->Enable( true );
305 }
306
307 // Propagate all the way out so that the MODEL_ZONES_OVERVIEW can pick it up
308 if( m_settings )
309 m_settings->m_Netcode = m_netSelector->GetSelectedNetcode();
310
311 if( m_settings && m_zone )
312 {
313 m_zonesSettingsBag.GetZoneSettings( m_zone )->m_Netcode = m_settings->m_Netcode;
314 m_zone->SetNetCode( m_settings->m_Netcode, true );
315 }
316
317 wxCommandEvent* evt = new wxCommandEvent( EVT_ZONE_NET_UPDATE );
318 wxQueueEvent( m_parent, evt );
319}
320
321
323{
324 if( !CommitPendingChanges() )
325 return false;
326
327 if( !m_settings )
328 return false;
329
330 if( !AcceptOptions() )
331 return false;
332
333 return true;
334}
335
336
338{
339 return m_layerSpecificOverrides->CommitPendingChanges();
340}
341
342
343bool PANEL_ZONE_PROPERTIES::AcceptOptions( bool aUseExportableSetupOnly )
344{
345 if( !m_clearance.Validate( 0, pcbIUScale.mmToIU( ZONE_CLEARANCE_MAX_VALUE_MM ) ) )
346 return false;
347
348 if( !m_minWidth.Validate( pcbIUScale.mmToIU( ZONE_THICKNESS_MIN_VALUE_MM ), INT_MAX ) )
349 return false;
350
351 if( !m_cornerRadius.Validate( 0, INT_MAX ) )
352 return false;
353
354 if( !m_spokeWidth.Validate( 0, INT_MAX ) )
355 return false;
356
358
359 if( m_settings->m_FillMode == ZONE_FILL_MODE::HATCH_PATTERN )
360 {
361 int minThickness = m_minWidth.GetIntValue();
362
363 if( !m_gridStyleThickness.Validate( minThickness, INT_MAX ) )
364 return false;
365
366 if( !m_gridStyleGap.Validate( minThickness, INT_MAX ) )
367 return false;
368 }
369
370 switch( m_PadInZoneOpt->GetSelection() )
371 {
372 case 3: m_settings->SetPadConnection( ZONE_CONNECTION::NONE ); break;
373 case 2: m_settings->SetPadConnection( ZONE_CONNECTION::THT_THERMAL ); break;
374 case 1: m_settings->SetPadConnection( ZONE_CONNECTION::THERMAL ); break;
375 case 0: m_settings->SetPadConnection( ZONE_CONNECTION::FULL ); break;
376 }
377
378 switch( m_OutlineDisplayCtrl->GetSelection() )
379 {
380 case 0: m_settings->m_ZoneBorderDisplayStyle = ZONE_BORDER_DISPLAY_STYLE::NO_HATCH; break;
381 case 1: m_settings->m_ZoneBorderDisplayStyle = ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE; break;
382 case 2: m_settings->m_ZoneBorderDisplayStyle = ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_FULL; break;
383 }
384
387 {
388 return false;
389 }
390
391 m_settings->m_BorderHatchPitch = m_outlineHatchPitch.GetIntValue();
392
393 m_settings->m_ZoneClearance = m_clearance.GetIntValue();
394 m_settings->m_ZoneMinThickness = m_minWidth.GetIntValue();
395
397 magic_enum::enum_cast<ZONE_SETTINGS::CORNER_SMOOTHING>( m_cornerSmoothingChoice->GetSelection() )
399
400 m_settings->SetCornerSmoothingType( smoothing );
401
402 if( m_settings->GetCornerSmoothingType() == ZONE_SETTINGS::CORNER_SMOOTHING::NO_SMOOTHING )
403 m_settings->SetCornerRadius( 0 );
404 else
405 m_settings->SetCornerRadius( m_cornerRadius.GetIntValue() );
406
407 m_settings->m_Locked = m_cbLocked->GetValue();
408
409 m_settings->m_ThermalReliefGap = m_antipadClearance.GetValue();
410 m_settings->m_ThermalReliefSpokeWidth = m_spokeWidth.GetValue();
411
412 if( m_settings->m_ThermalReliefSpokeWidth < m_settings->m_ZoneMinThickness )
413 {
414 DisplayErrorMessage( this, _( "Thermal spoke width cannot be smaller than the minimum width." ) );
415 return false;
416 }
417
418 m_settings->SetIslandRemovalMode( (ISLAND_REMOVAL_MODE) m_cbRemoveIslands->GetSelection() );
419 m_settings->SetMinIslandArea( m_islandThreshold.GetValue() );
420
421 // If we use only exportable to others zones parameters, exit here:
422 if( aUseExportableSetupOnly )
423 return true;
424
425 if( m_netSelector->IsShown() )
426 m_settings->m_Netcode = m_netSelector->GetSelectedNetcode();
427
428 m_settings->m_Name = m_tcZoneName->GetValue();
429
430 if( BOARD* board = m_frame->GetBoard() )
431 m_settings->m_Name = board->GetUniqueZoneName( m_settings->m_Name, m_zone );
432
433 // Avoid clobbering theiving zone properties
435 {
436 m_settings->m_FillMode = m_cbHatched->GetValue() ? ZONE_FILL_MODE::HATCH_PATTERN
438 }
439 m_settings->m_HatchOrientation = m_gridStyleRotation.GetAngleValue();
440 m_settings->m_HatchThickness = m_gridStyleThickness.GetIntValue();
441 m_settings->m_HatchGap = m_gridStyleGap.GetIntValue();
442 m_settings->m_HatchSmoothingLevel = m_spinCtrlSmoothLevel->GetValue();
443 m_settings->m_HatchSmoothingValue = m_spinCtrlSmoothValue->GetValue();
444
445 for( auto& [layer, props] : m_settings->m_LayerProperties )
446 props.hatching_offset = std::nullopt;
447
448 for( const auto& [layer, props] : m_layerPropsTable->GetItems() )
449 m_settings->m_LayerProperties[layer] = props;
450
451 return true;
452}
453
454
455void PANEL_ZONE_PROPERTIES::onHatched( wxCommandEvent& event )
456{
457 bool enable = m_cbHatched->GetValue();
458 m_gridStyleThickness.Enable( enable );
459 m_gridStyleGap.Enable( enable );
460 m_gridStyleRotation.Enable( enable );
461 m_staticTextGridSmoothingLevel->Enable( enable );
462 m_spinCtrlSmoothLevel->Enable( enable );
463 m_staticTextGridSmootingVal->Enable( enable );
464 m_spinCtrlSmoothValue->Enable( enable );
465 m_offsetOverridesLabel->Enable( enable );
466 m_layerSpecificOverrides->Enable( enable );
467 m_bpAddCustomLayer->Enable( enable );
468 m_bpDeleteCustomLayer->Enable( enable );
469}
470
471
472void PANEL_ZONE_PROPERTIES::OnAddLayerItem( wxCommandEvent& event )
473{
474 m_layerSpecificOverrides->OnAddRow(
475 [&]() -> std::pair<int, int>
476 {
477 if( !m_layerSpecificOverrides->GetTable()->AppendRows( 1 ) )
478 {
479 m_copperZoneInfoBar->ShowMessageFor( _( "All zone layers already overridden." ), 8000,
480 wxICON_WARNING );
481 }
482
483 return { m_layerSpecificOverrides->GetNumberRows() - 1, -1 };
484 } );
485}
486
487
488void PANEL_ZONE_PROPERTIES::OnDeleteLayerItem( wxCommandEvent& event )
489{
490 m_layerSpecificOverrides->OnDeleteRows(
491 [&]( int row )
492 {
493 m_layerSpecificOverrides->GetTable()->DeleteRows( row, 1 );
494 } );
495}
496
497
499{
500 if( m_netSelector->GetSelectedNetcode() <= INVALID_NET_CODE && !m_copperZoneInfoBar->IsShown() )
501 {
502 m_copperZoneInfoBar->ShowMessage( _( "<no net> will result in an isolated copper island." ), wxICON_WARNING );
503 }
504 else if( m_copperZoneInfoBar->IsShown() )
505 {
506 m_copperZoneInfoBar->Dismiss();
507 }
508}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
BASE_SET & set(size_t pos)
Definition base_set.h:126
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition dialog_shim.h:80
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition grid_tricks.h:57
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static LSET AllNonCuMask()
Return a mask holding all layer minus CU layers.
Definition lset.cpp:623
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
PANEL_ZONE_PROPERTIES_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxTAB_TRAVERSAL, const wxString &name=wxEmptyString)
void OnAddLayerItem(wxCommandEvent &event) override
static constexpr int INVALID_NET_CODE
void OnZoneNameChanged(wxCommandEvent &event) override
void onNetSelector(wxCommandEvent &aEvent)
bool AcceptOptions(bool aUseExportableSetupOnly=false)
PANEL_ZONE_PROPERTIES(wxWindow *aParent, PCB_BASE_FRAME *aFrame, ZONE_SETTINGS_BAG &aZonesSettingsBag, bool allowNetSpec=true)
void onHatched(wxCommandEvent &event) override
void OnRemoveIslandsSelection(wxCommandEvent &event) override
ZONE_SETTINGS_BAG & m_zonesSettingsBag
void OnDeleteLayerItem(wxCommandEvent &event) override
std::shared_ptr< ZONE_SETTINGS > m_settings
void OnCornerSmoothingSelection(wxCommandEvent &event) override
LAYER_PROPERTIES_GRID_TABLE * m_layerPropsTable
Base PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer.
Handle a list of polygons defining a copper zone.
Definition zone.h:70
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
This file is part of the common library.
#define _(s)
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
wxDEFINE_EVENT(EVT_ZONE_NAME_UPDATE, wxCommandEvent)
std::optional< VECTOR2I > hatching_offset
T NormalizeAngle180(T Angle)
Normalize angle to be in the -180.0 .
Definition trigo.h:202
ISLAND_REMOVAL_MODE
Whether or not to remove isolated islands from a zone.
#define ZONE_CLEARANCE_MAX_VALUE_MM
Definition zones.h:33
@ THERMAL
Use thermal relief for pads.
Definition zones.h:46
@ THT_THERMAL
Thermal relief only for THT pads.
Definition zones.h:48
@ NONE
Pads are not covered.
Definition zones.h:45
@ FULL
pads are covered by copper
Definition zones.h:47
#define ZONE_BORDER_HATCH_MINDIST_MM
Definition zones.h:35
#define ZONE_THICKNESS_MIN_VALUE_MM
Definition zones.h:31
#define ZONE_BORDER_HATCH_MAXDIST_MM
Definition zones.h:36