KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_drc_rule_editor.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) 2024 KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20
21#include <drc/drc_engine.h>
22#include <widgets/wx_infobar.h>
25
26#include <wx/log.h>
27#include <confirm.h>
28#include <pcb_edit_frame.h>
29#include <kiface_base.h>
30#include <drc/drc_rule_parser.h>
31
47#include "drc_re_rule_loader.h"
48#include "drc_re_rule_saver.h"
49#include <drc/drc_engine.h>
51#include <tool/tool_manager.h>
52#include <tool/actions.h>
53#include <wx/ffile.h>
54#include <functional>
55#include <memory>
56#include <set>
57
58
59const RULE_TREE_NODE* FindNodeById( const std::vector<RULE_TREE_NODE>& aNodes, int aTargetId )
60{
61 auto it = std::find_if( aNodes.begin(), aNodes.end(),
62 [aTargetId]( const RULE_TREE_NODE& node )
63 {
64 return node.m_nodeId == aTargetId;
65 } );
66
67 if( it != aNodes.end() )
68 {
69 return &( *it );
70 }
71
72 return nullptr;
73}
74
75
77 RULE_EDITOR_DIALOG_BASE( aParent, _( "Design Rule Editor" ), wxSize( 980, 800 ) ),
79 m_reporter( nullptr ),
80 m_nodeId( 0 )
81{
82 m_frame = aEditorFrame;
83 m_currentBoard = m_frame->GetBoard();
84 m_ruleEditorPanel = nullptr;
85
86 m_ruleTreeCtrl->DeleteAllItems();
87
89
91
93
95
96 if( Prj().IsReadOnly() )
97 {
98 m_infoBar->ShowMessage( _( "Project is missing or read-only. Settings will not be "
99 "editable." ),
100 wxICON_WARNING );
101 }
102
103 m_severities = 0;
104
105 m_markersProvider = std::make_shared<DRC_ITEMS_PROVIDER>( m_currentBoard, MARKER_BASE::MARKER_DRC,
107
109 new wxDataViewCtrl( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_ROW_LINES | wxDV_SINGLE );
110
112 m_markerDataView->AssociateModel( m_markersTreeModel );
114
115 m_markerDataView->Hide();
116}
117
118
122
123
125{
127
128 Layout();
129 SetMinSize( wxSize( 400, 500 ) );
130 SetSize( m_initialSize );
131
132 wxLogTrace( "debug_dlg_size", "DRC TransferDataToWindow: size=%s minSize=%s",
133 GetSize().IsFullySpecified() ? wxString::Format( "%dx%d", GetSize().x, GetSize().y )
134 : wxString( "default" ),
135 GetMinSize().IsFullySpecified()
136 ? wxString::Format( "%dx%d", GetMinSize().x, GetMinSize().y )
137 : wxString( "default" ) );
138
139 return ok;
140}
141
142
144{
146 return false;
147
149
150 return true;
151}
152
153
155{
156 std::vector<RULE_TREE_NODE> result;
157
158 int lastParentId;
159 int electricalItemId;
160 int manufacturabilityItemId;
161 int highSpeedDesignId;
162 int footprintItemId;
163
164 result.push_back( buildRuleTreeNodeData( "Design Rules", DRC_RULE_EDITOR_ITEM_TYPE::ROOT ) );
165 lastParentId = m_nodeId;
166
167 result.push_back( buildRuleTreeNodeData( "Electrical", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, lastParentId ) );
168 electricalItemId = m_nodeId;
169
170 result.push_back( buildRuleTreeNodeData( "Manufacturability", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, lastParentId ) );
171 manufacturabilityItemId = m_nodeId;
172
173 result.push_back( buildRuleTreeNodeData( "Highspeed design", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, lastParentId ) );
174 highSpeedDesignId = m_nodeId;
175
176 result.push_back( buildRuleTreeNodeData( "Footprints", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, lastParentId ) );
177 footprintItemId = m_nodeId;
178
179 std::vector<RULE_TREE_NODE> subItemNodes = buildElectricalRuleTreeNodes( electricalItemId );
180 result.insert( result.end(), subItemNodes.begin(), subItemNodes.end() );
181
182 subItemNodes = buildManufacturabilityRuleTreeNodes( manufacturabilityItemId );
183 result.insert( result.end(), subItemNodes.begin(), subItemNodes.end() );
184
185 subItemNodes = buildHighspeedDesignRuleTreeNodes( highSpeedDesignId );
186 result.insert( result.end(), subItemNodes.begin(), subItemNodes.end() );
187
188 subItemNodes = buildFootprintsRuleTreeNodes( footprintItemId );
189 result.insert( result.end(), subItemNodes.begin(), subItemNodes.end() );
190
191 // Custom rules category
192 result.push_back( buildRuleTreeNodeData( "Custom", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, lastParentId ) );
193 int customItemId = m_nodeId;
194 result.push_back(
196
197 return result;
198}
199
201{
202 wxFileName rulesFile( m_frame->GetDesignRulesPath() );
203
204 if( !rulesFile.FileExists() )
205 return;
206
207 DRC_RULE_LOADER loader;
208 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFile( rulesFile.GetFullPath() );
209
210 if( entries.empty() )
211 return;
212
213 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
214 wxS( "[LoadExistingRules] Loaded %zu entries from %s" ),
215 entries.size(), rulesFile.GetFullPath() );
216
217 // Build lookup maps before the loading loop to avoid O(n) scans per rule.
218 // constraintTypeToNodeId maps panel type → parent node ID (from m_ruleTreeNodeDatas).
219 // m_treeHistoryData already maps node ID → wxTreeItemId (populated by InitRuleTreeItems).
220 std::unordered_map<int, int> constraintTypeToNodeId;
221
222 for( const RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
223 {
224 if( node.m_nodeType == CONSTRAINT && node.m_nodeTypeMap )
225 {
226 constraintTypeToNodeId[*node.m_nodeTypeMap] = node.m_nodeId;
227
228 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
229 wxS( "[LoadExistingRules] Node '%s': nodeId=%d, m_nodeTypeMap=%d" ),
230 wxString( node.m_nodeName ), node.m_nodeId,
231 static_cast<int>( *node.m_nodeTypeMap ) );
232 }
233 }
234
235 // Suppress selection-change events and repaint during bulk loading. Without this,
236 // each AppendNewRuleTreeItem triggers SelectItem which creates and immediately
237 // destroys a full PANEL_DRC_RULE_EDITOR (regex compilation, Scintilla, layout)
238 // for every rule. On Windows this causes >11s load times for moderate rule sets.
239 m_ruleTreeCtrl->Freeze();
241
242 for( DRC_RE_LOADED_PANEL_ENTRY& entry : entries )
243 {
244 DRC_RULE_EDITOR_CONSTRAINT_NAME type = entry.panelType;
245
246 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
247 wxS( "[LoadExistingRules] Processing entry: rule='%s', panelType=%d" ),
248 entry.ruleName, static_cast<int>( type ) );
249
250 auto typeIt = constraintTypeToNodeId.find( static_cast<int>( type ) );
251
252 if( typeIt == constraintTypeToNodeId.end() )
253 {
254 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
255 wxS( "[LoadExistingRules] No parent found for panelType=%d, skipping" ),
256 static_cast<int>( type ) );
257 continue;
258 }
259
260 int parentId = typeIt->second;
261
262 auto histIt = m_treeHistoryData.find( parentId );
263
264 if( histIt == m_treeHistoryData.end() )
265 {
266 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
267 wxS( "[LoadExistingRules] Tree item not found for parentId=%d, skipping" ),
268 parentId );
269 continue;
270 }
271
272 wxTreeItemId parentItem = std::get<2>( histIt->second );
273
274 if( !parentItem.IsOk() )
275 {
276 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
277 wxS( "[LoadExistingRules] Tree item not valid for parentId=%d, skipping" ),
278 parentId );
279 continue;
280 }
281
282 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
283 wxS( "[LoadExistingRules] Found parent node: parentId=%d" ), parentId );
284
285 RULE_TREE_NODE node =
286 buildRuleTreeNodeData( entry.ruleName, RULE, parentId, type );
287
288 auto ruleData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( entry.constraintData );
289
290 if( ruleData )
291 {
292 ruleData->SetId( node.m_nodeData->GetId() );
293 ruleData->SetParentId( parentId );
294 ruleData->SetOriginalRuleText( entry.originalRuleText );
295 ruleData->SetWasEdited( entry.wasEdited );
296 ruleData->SetLayerSource( entry.layerSource );
297
298 if( !entry.layerSource.IsEmpty() )
299 ruleData->SetLayers( entry.layerCondition.Seq() );
300
301 ruleData->SetSeverity( entry.severity );
302 node.m_nodeData = ruleData;
303 }
304
305 m_ruleTreeNodeDatas.push_back( node );
306 AppendNewRuleTreeItem( node, parentItem );
307
308 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
309 wxS( "[LoadExistingRules] Appended rule '%s' to tree under parentId=%d" ),
310 entry.ruleName, parentId );
311 }
312
314 m_ruleTreeCtrl->Thaw();
315}
316
317
319{
320 wxTreeItemId treeItemId;
321 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( aRuleTreeItemData->GetNodeId() );
322
323 if( nodeDetail->m_nodeType == CONSTRAINT )
324 {
325 treeItemId = aRuleTreeItemData->GetTreeItemId();
326 }
327 else
328 {
329 treeItemId = aRuleTreeItemData->GetParentTreeItemId();
330 }
331
332 AppendNewRuleTreeItem( buildRuleTreeNode( aRuleTreeItemData ), treeItemId );
333 SetModified();
334}
335
336
338{
339 RULE_TREE_NODE* sourceTreeNode = getRuleTreeNodeInfo( aRuleTreeItemData->GetNodeId() );
340
341 auto sourceDataPtr = dynamic_pointer_cast<RULE_EDITOR_DATA_BASE>( sourceTreeNode->m_nodeData );
342
343 if( !sourceDataPtr )
344 return;
345
346 // Strip any trailing " <number>" suffix so the number increments
347 wxString baseName = sourceDataPtr->GetRuleName();
348 int lastSpace = baseName.Find( ' ', true );
349
350 if( lastSpace != wxNOT_FOUND )
351 {
352 wxString suffix = baseName.Mid( lastSpace + 1 );
353 long num;
354
355 if( suffix.ToLong( &num ) )
356 baseName = baseName.Left( lastSpace );
357 }
358
359 RULE_TREE_NODE targetTreeNode = buildRuleTreeNode( aRuleTreeItemData, baseName );
360 targetTreeNode.m_nodeData->CopyFrom( *sourceDataPtr );
361
362 wxTreeItemId treeItemId = aRuleTreeItemData->GetParentTreeItemId();
363 AppendNewRuleTreeItem( targetTreeNode, treeItemId );
364 SetModified();
365}
366
367
369{
370 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( aCurrentRuleTreeItemData->GetNodeId() );
371
372 // Freeze so panel creation and data population happen off-screen.
373 m_scrolledContentWin->Freeze();
374
375 if( nodeDetail->m_nodeType == ROOT || nodeDetail->m_nodeType == CATEGORY || nodeDetail->m_nodeType == CONSTRAINT )
376 {
377 std::vector<RULE_TREE_NODE*> ruleNodes;
378 collectChildRuleNodes( nodeDetail->m_nodeId, ruleNodes );
379
380 std::vector<DRC_RULE_ROW> rows;
381 rows.reserve( ruleNodes.size() );
382
383 for( RULE_TREE_NODE* ruleNode : ruleNodes )
384 {
385 RULE_TREE_NODE* parentNode = getRuleTreeNodeInfo( ruleNode->m_nodeData->GetParentId() );
386 wxString type = parentNode ? parentNode->m_nodeName : wxString{};
387 rows.push_back( { type, ruleNode->m_nodeData->GetRuleName(), ruleNode->m_nodeData->GetComment() } );
388 }
389
392 m_ruleEditorPanel = nullptr;
393 }
394 else if( nodeDetail->m_nodeType == RULE )
395 {
396 RULE_TREE_ITEM_DATA* parentItemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
397 m_ruleTreeCtrl->GetItemData( aCurrentRuleTreeItemData->GetParentTreeItemId() ) );
398 RULE_TREE_NODE* paretNodeDetail = getRuleTreeNodeInfo( parentItemData->GetNodeId() );
399 wxString constraintName = paretNodeDetail->m_nodeName;
400
402 m_scrolledContentWin, m_frame->GetBoard(),
403 static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( nodeDetail->m_nodeTypeMap.value_or( -1 ) ),
404 &constraintName, dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( nodeDetail->m_nodeData ) );
405
407 m_ruleEditorPanel->TransferDataToWindow();
408
409 m_ruleEditorPanel->SetSaveCallback(
410 [this]( int aNodeId )
411 {
412 this->saveRule( aNodeId );
413 } );
414
415 m_ruleEditorPanel->SetRemoveCallback(
416 [this]( int aNodeId )
417 {
418 this->RemoveRule( aNodeId );
419 } );
420
421 m_ruleEditorPanel->SetCloseCallback(
422 [this]( int aNodeId )
423 {
424 this->closeRuleEntryView( aNodeId );
425 } );
426
427 m_ruleEditorPanel->SetRuleNameValidationCallback(
428 [this]( int aNodeId, wxString aRuleName )
429 {
430 return this->validateRuleName( aNodeId, aRuleName );
431 } );
432
433 m_ruleEditorPanel->SetShowMatchesCallBack(
434 [this]( int aNodeId ) -> int
435 {
436 return this->highlightMatchingItems( aNodeId );
437 } );
438
439 m_groupHeaderPanel = nullptr;
440 }
441
442 m_scrolledContentWin->Thaw();
443}
444
445
446void DIALOG_DRC_RULE_EDITOR::OnSave( wxCommandEvent& aEvent )
447{
449 m_ruleEditorPanel->Save( aEvent );
450}
451
452
453void DIALOG_DRC_RULE_EDITOR::OnCancel( wxCommandEvent& aEvent )
454{
455 // If currently editing a panel, cancel that first
457 {
458 auto data = m_ruleEditorPanel->GetConstraintData();
459 bool isNew = data && data->IsNew();
460
461 m_ruleEditorPanel->Cancel( aEvent );
462
463 if( isNew )
464 {
465 // After canceling a new rule, check if there are any remaining modified rules
466 std::vector<RULE_TREE_NODE*> modifiedRules;
467 collectModifiedRules( modifiedRules );
468
469 if( modifiedRules.empty() )
471
472 return;
473 }
474 }
475
476 // If there are unsaved changes, prompt the user
477 if( IsModified() )
478 {
480
481 if( result == wxID_CANCEL )
482 return;
483
484 if( result == wxID_YES )
485 {
486 // Validate all rules before saving
487 std::map<wxString, wxString> errors;
488
489 if( !validateAllRules( errors ) )
490 {
491 // Find the first rule with an error and select it
493 {
494 if( errors.find( node.m_nodeName ) != errors.end() )
495 {
496 selectRuleNode( node.m_nodeId );
497
498 wxString msg = wxString::Format(
499 _( "Cannot save due to validation errors in rule '%s':\n\n%s" ),
500 node.m_nodeName, errors[node.m_nodeName] );
501 DisplayErrorMessage( this, msg );
502 return;
503 }
504 }
505
506 return;
507 }
508
511 }
512 }
513
514 // Purge unsaved new rules from memory so they don't reappear on reopen
515 std::vector<int> newRuleNodeIds;
516
517 for( const RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
518 {
519 if( node.m_nodeType == RULE && node.m_nodeData && node.m_nodeData->IsNew() )
520 newRuleNodeIds.push_back( node.m_nodeId );
521 }
522
523 for( int nodeId : newRuleNodeIds )
524 {
525 auto it = m_treeHistoryData.find( nodeId );
526
527 if( it != m_treeHistoryData.end() )
528 DeleteRuleTreeItem( std::get<2>( it->second ), nodeId );
529
530 deleteTreeNodeData( nodeId );
531 }
532
533 aEvent.Skip();
534}
535
536
538{
539 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( aRuleTreeItemData->GetNodeId() );
540
542 {
543 m_ruleEditorPanel->TransferDataFromWindow();
544
545 nodeDetail->m_nodeName = nodeDetail->m_nodeData->GetRuleName();
546 nodeDetail->m_nodeData->SetIsNew( false );
547
548 // Mark as edited so the rule gets regenerated instead of using original text
549 auto constraintData =
550 std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( nodeDetail->m_nodeData );
551
552 if( constraintData )
553 constraintData->SetWasEdited( true );
554
555 UpdateRuleTreeItemText( aRuleTreeItemData->GetTreeItemId(), nodeDetail->m_nodeName );
556 }
557}
558
559
578
579
581{
582 RULE_TREE_ITEM_DATA* itemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
583 m_ruleTreeCtrl->GetItemData( GetCurrentlySelectedRuleTreeItemData()->GetTreeItemId() ) );
584 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( itemData->GetNodeId() );
585
586 if( !nodeDetail->m_nodeData->IsNew() )
587 {
588 if( OKOrCancelDialog( this, _( "Confirmation" ), "", _( "Are you sure you want to delete?" ), _( "Delete" ) )
589 != wxID_OK )
590 {
591 return;
592 }
593 }
594
595 if( itemData )
596 {
597 int nodeId = itemData->GetNodeId();
598
599 SetModified();
600 DeleteRuleTreeItem( GetCurrentlySelectedRuleTreeItemData()->GetTreeItemId(), nodeId );
601 deleteTreeNodeData( nodeId );
604 }
605
606 SetControlsEnabled( true );
607}
608
609
610std::vector<RULE_TREE_NODE> DIALOG_DRC_RULE_EDITOR::buildElectricalRuleTreeNodes( int& aParentId )
611{
612 std::vector<RULE_TREE_NODE> result;
613 int lastParentId;
614
615 result.push_back( buildRuleTreeNodeData( "Clearance", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
616 lastParentId = m_nodeId;
617
618 result.push_back( buildRuleTreeNodeData( "Minimum clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
620 result.push_back( buildRuleTreeNodeData( "Copper to edge clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
621 lastParentId, COPPER_TO_EDGE_CLEARANCE ) );
622 result.push_back( buildRuleTreeNodeData( "Courtyard clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
624 result.push_back( buildRuleTreeNodeData( "Physical clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
626 result.push_back( buildRuleTreeNodeData( "Creepage distance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
628
629 result.push_back( buildRuleTreeNodeData( "Minimum connection width", CONSTRAINT, aParentId,
631 result.push_back( buildRuleTreeNodeData( "Copper to hole clearance", CONSTRAINT, aParentId,
633 result.push_back( buildRuleTreeNodeData( "Minimum thermal relief spoke count", CONSTRAINT, aParentId,
635
636 return result;
637}
638
639
640std::vector<RULE_TREE_NODE> DIALOG_DRC_RULE_EDITOR::buildManufacturabilityRuleTreeNodes( int& aParentId )
641{
642 std::vector<RULE_TREE_NODE> result;
643 int lastParentId;
644
645 result.push_back( buildRuleTreeNodeData( "Minimum annular width", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
646 aParentId, MINIMUM_ANNULAR_WIDTH ) );
647
648 result.push_back( buildRuleTreeNodeData( "Hole", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
649 lastParentId = m_nodeId;
651 lastParentId, MINIMUM_DRILL_SIZE ) );
652 result.push_back( buildRuleTreeNodeData( "Hole to hole distance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
653 lastParentId, HOLE_TO_HOLE_DISTANCE ) );
654 result.push_back(
656
657 result.push_back( buildRuleTreeNodeData( "Minimum text height and thickness", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
659
660 result.push_back( buildRuleTreeNodeData( "Silk to silk clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
661 aParentId, SILK_TO_SILK_CLEARANCE ) );
662 result.push_back( buildRuleTreeNodeData( "Silk to soldermask clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
663 aParentId, SILK_TO_SOLDERMASK_CLEARANCE ) );
664
665 result.push_back( buildRuleTreeNodeData( "Minimum soldermask sliver", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
666 aParentId, MINIMUM_SOLDERMASK_SLIVER ) );
667 result.push_back( buildRuleTreeNodeData( "Soldermask expansion", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
668 aParentId, SOLDERMASK_EXPANSION ) );
669
670 result.push_back( buildRuleTreeNodeData( "Solderpaste expansion", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
671 aParentId, SOLDERPASTE_EXPANSION ) );
672
673 return result;
674}
675
676
677std::vector<RULE_TREE_NODE> DIALOG_DRC_RULE_EDITOR::buildHighspeedDesignRuleTreeNodes( int& aParentId )
678{
679 std::vector<RULE_TREE_NODE> result;
680
681 result.push_back(
683 result.push_back( buildRuleTreeNodeData( "Maximum via count", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
685 result.push_back( buildRuleTreeNodeData( "Routing diff pair", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
687 result.push_back( buildRuleTreeNodeData( "Matched length diff pair", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
688 aParentId, MATCHED_LENGTH_DIFF_PAIR ) );
689 result.push_back( buildRuleTreeNodeData( "Absolute length", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
690 ABSOLUTE_LENGTH ) );
691
692 return result;
693}
694
695
696std::vector<RULE_TREE_NODE> DIALOG_DRC_RULE_EDITOR::buildFootprintsRuleTreeNodes( int& aParentId )
697{
698 std::vector<RULE_TREE_NODE> result;
699 result.push_back( buildRuleTreeNodeData( "Permitted layers", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
701 result.push_back( buildRuleTreeNodeData( "Allowed orientation", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
703 result.push_back( buildRuleTreeNodeData( "Vias under SMD", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
704 VIAS_UNDER_SMD ) );
705
706 return result;
707}
708
709
717bool nodeExists( const RULE_TREE_NODE& aRuleTreeNode, const wxString& aTargetName )
718{
719 if( aRuleTreeNode.m_nodeName == aTargetName )
720 {
721 return true;
722 }
723
724 for( const auto& child : aRuleTreeNode.m_childNodes )
725 {
726 if( nodeExists( child, aTargetName ) )
727 {
728 return true;
729 }
730 }
731
732 return false;
733}
734
735
743bool nodeExists( const std::vector<RULE_TREE_NODE>& aRuleTreeNodes, const wxString& aTargetName )
744{
745 for( const auto& node : aRuleTreeNodes )
746 {
747 if( nodeExists( node, aTargetName ) )
748 {
749 return true;
750 }
751 }
752
753 return false;
754}
755
756
758 const wxString& aBaseName )
759{
760 // Factory function type for creating constraint data objects
761 using ConstraintDataFactory =
762 std::function<std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA>( const DRC_RE_BASE_CONSTRAINT_DATA& )>;
763
764 // Factory map for constraint data creation
765 static const std::unordered_map<DRC_RULE_EDITOR_CONSTRAINT_NAME, ConstraintDataFactory> s_constraintFactories = {
767 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
768 {
769 return std::make_shared<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>( data );
770 } },
772 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
773 {
774 return std::make_shared<DRC_RE_MINIMUM_TEXT_HEIGHT_THICKNESS_CONSTRAINT_DATA>( data );
775 } },
777 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
778 {
779 return std::make_shared<DRC_RE_ROUTING_DIFF_PAIR_CONSTRAINT_DATA>( data );
780 } },
782 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
783 {
784 return std::make_shared<DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA>( data );
785 } },
787 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
788 {
789 return std::make_shared<DRC_RE_PERMITTED_LAYERS_CONSTRAINT_DATA>( data );
790 } },
792 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
793 {
794 return std::make_shared<DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA>( data );
795 } },
797 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
798 {
799 return std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>( data );
800 } },
802 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
803 {
804 return std::make_shared<DRC_RE_ABSOLUTE_LENGTH_TWO_CONSTRAINT_DATA>( data );
805 } },
807 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
808 {
809 return std::make_shared<DRC_RE_MATCHED_LENGTH_DIFF_PAIR_CONSTRAINT_DATA>( data );
810 } },
812 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
813 {
814 return std::make_shared<DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA>( data );
815 } }
816 };
817
818 RULE_TREE_ITEM_DATA* treeItemData;
819 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( aRuleTreeItemData->GetNodeId() );
820
821 if( nodeDetail->m_nodeType == CONSTRAINT )
822 {
823 treeItemData = aRuleTreeItemData;
824 }
825 else
826 {
827 treeItemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
828 m_ruleTreeCtrl->GetItemData( aRuleTreeItemData->GetParentTreeItemId() ) );
829 nodeDetail = getRuleTreeNodeInfo( treeItemData->GetNodeId() );
830 }
831
832 m_nodeId++;
833
834 wxString base = aBaseName.IsEmpty() ? nodeDetail->m_nodeName : aBaseName;
835 wxString nodeName = base + " 1";
836
837 int loop = 2;
838 bool check = false;
839
840 do
841 {
842 check = false;
843
844 if( nodeExists( m_ruleTreeNodeDatas, nodeName ) )
845 {
846 nodeName = base + wxString::Format( " %d", loop );
847 loop++;
848 check = true;
849 }
850 } while( check );
851
853 nodeName, RULE, nodeDetail->m_nodeId,
854 static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( nodeDetail->m_nodeTypeMap.value_or( 0 ) ), {}, m_nodeId );
855
856 auto nodeType = static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( newRuleNode.m_nodeTypeMap.value_or( -1 ) );
857
858 DRC_RE_BASE_CONSTRAINT_DATA clearanceData( m_nodeId, nodeDetail->m_nodeData->GetId(), newRuleNode.m_nodeName );
859
860 if( s_constraintFactories.find( nodeType ) != s_constraintFactories.end() )
861 {
862 newRuleNode.m_nodeData = s_constraintFactories.at( nodeType )( clearanceData );
863 }
864 else if( DRC_RULE_EDITOR_UTILS::IsNumericInputType( nodeType ) )
865 {
866 newRuleNode.m_nodeData = DRC_RULE_EDITOR_UTILS::CreateNumericConstraintData( nodeType, clearanceData );
867 }
868 else if( DRC_RULE_EDITOR_UTILS::IsBoolInputType( nodeType ) )
869 {
870 newRuleNode.m_nodeData = std::make_shared<DRC_RE_BOOL_INPUT_CONSTRAINT_DATA>( clearanceData );
871 }
872 else
873 {
874 wxLogWarning( "No factory found for constraint type: %d", nodeType );
875 newRuleNode.m_nodeData = std::make_shared<DRC_RE_BASE_CONSTRAINT_DATA>( clearanceData );
876 }
877
878 std::static_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( newRuleNode.m_nodeData )
879 ->SetConstraintCode( DRC_RULE_EDITOR_UTILS::ConstraintToKicadDrc( nodeType ) );
880 newRuleNode.m_nodeData->SetIsNew( true );
881
882 m_ruleTreeNodeDatas.push_back( newRuleNode );
883
884 return newRuleNode;
885}
886
887
889{
890 auto it = std::find_if( m_ruleTreeNodeDatas.begin(), m_ruleTreeNodeDatas.end(),
891 [aNodeId]( const RULE_TREE_NODE& node )
892 {
893 return node.m_nodeId == aNodeId;
894 } );
895
896 if( it != m_ruleTreeNodeDatas.end() )
897 {
898 return &( *it ); // Return pointer to the found node
899 }
900 else
901 return nullptr;
902}
903
904
906{
907 if( !m_ruleEditorPanel->GetIsValidationSucceeded() )
908 {
909 wxString validationMessage = m_ruleEditorPanel->GetValidationMessage();
910
911 DisplayErrorMessage( this, validationMessage );
912 }
913 else
914 {
915 RULE_TREE_ITEM_DATA* itemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
916 m_ruleTreeCtrl->GetItemData( GetCurrentlySelectedRuleTreeItemData()->GetTreeItemId() ) );
917
918 if( itemData )
919 {
920 UpdateRuleTypeTreeItemData( itemData );
921 }
922
925
926 SetControlsEnabled( true );
927 }
928}
929
930
932{
933 SetControlsEnabled( true );
934}
935
936
938{
939 (void) aNodeId;
940
941 if( !m_ruleEditorPanel )
942 return -1;
943
944 // Ensure we use the latest text from the condition editor
945 m_ruleEditorPanel->TransferDataFromWindow();
946
947 std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA> constraintData = m_ruleEditorPanel->GetConstraintData();
948 std::shared_ptr<DRC_RULE> selectedRule;
949 wxString condition;
950 wxString ruleText = constraintData->GetGeneratedRule();
951
952 if( ruleText.IsEmpty() )
953 {
954 m_frame->FocusOnItems( {} );
955 Raise();
956 return 0;
957 }
958
959 wxString fullText = wxS( "(version 1)\n" ) + ruleText;
960
961 try
962 {
963 std::vector<std::shared_ptr<DRC_RULE>> rules;
964 DRC_RULES_PARSER parser( fullText, wxS( "ShowMatches" ) );
965 parser.Parse( rules, nullptr );
966
967 if( rules.empty() )
968 {
969 m_frame->FocusOnItems( {} );
970 Raise();
971 return 0;
972 }
973
974 selectedRule = rules[0];
975 condition = selectedRule->m_Condition ? selectedRule->m_Condition->GetExpression() : wxString();
976
977 if( selectedRule->m_Condition && !selectedRule->m_Condition->GetExpression().IsEmpty()
978 && !selectedRule->m_Condition->Compile( nullptr ) )
979 {
980 return -1;
981 }
982 }
983 catch( PARSE_ERROR& )
984 {
985 return -1;
986 }
987
988 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ), wxS( "[ShowMatches] nodeId=%d, condition='%s'" ), aNodeId,
989 condition );
990
991 m_drcTool = m_frame->GetToolManager()->GetTool<DRC_TOOL>();
992
993 std::vector<BOARD_ITEM*> allMatches;
994
995 allMatches = m_drcTool->GetDRCEngine()->GetItemsMatchingRule( selectedRule, m_reporter );
996
997 // Filter out items without visible geometry
998 std::vector<BOARD_ITEM*> matches;
999
1000 for( BOARD_ITEM* item : allMatches )
1001 {
1002 switch( item->Type() )
1003 {
1004 case PCB_NETINFO_T:
1005 case PCB_GENERATOR_T:
1006 case PCB_GROUP_T:
1007 continue;
1008
1009 default:
1010 matches.push_back( item );
1011 break;
1012 }
1013 }
1014
1015 int matchCount = static_cast<int>( matches.size() );
1016
1017 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ), wxS( "[ShowMatches] matched_count=%d (filtered from %zu)" ),
1018 matchCount, allMatches.size() );
1019
1020 // Clear any existing selection and select matched items
1021 m_frame->GetToolManager()->RunAction( ACTIONS::selectionClear );
1022
1023 if( matches.size() > 0 )
1024 {
1025 std::vector<EDA_ITEM*> selectItems;
1026
1027 for( BOARD_ITEM* item : matches )
1028 selectItems.push_back( item );
1029
1030 m_frame->GetToolManager()->RunAction( ACTIONS::selectItems, &selectItems );
1031 m_frame->GetToolManager()->RunAction( ACTIONS::zoomFitSelection );
1032 }
1033
1034 // Also brighten items to provide additional visual feedback
1035 m_frame->FocusOnItems( matches );
1036 Raise();
1037
1038 return matchCount;
1039}
1040
1041
1042bool DIALOG_DRC_RULE_EDITOR::validateRuleName( int aNodeId, const wxString& aRuleName )
1043{
1044 auto it = std::find_if( m_ruleTreeNodeDatas.begin(), m_ruleTreeNodeDatas.end(),
1045 [aNodeId, aRuleName]( const RULE_TREE_NODE& node )
1046 {
1047 return node.m_nodeName == aRuleName && node.m_nodeId != aNodeId
1048 && node.m_nodeType == RULE;
1049 } );
1050
1051 if( it != m_ruleTreeNodeDatas.end() )
1052 {
1053 return false;
1054 }
1055
1056 return true;
1057}
1058
1059
1061{
1062 size_t initial_size = m_ruleTreeNodeDatas.size();
1063
1064 m_ruleTreeNodeDatas.erase( std::remove_if( m_ruleTreeNodeDatas.begin(), m_ruleTreeNodeDatas.end(),
1065 [aNodeId]( const RULE_TREE_NODE& node )
1066 {
1067 return node.m_nodeId == aNodeId;
1068 } ),
1069 m_ruleTreeNodeDatas.end() );
1070
1071 if( m_ruleTreeNodeDatas.size() < initial_size )
1072 return true;
1073 else
1074 return false;
1075}
1076
1077
1078void DIALOG_DRC_RULE_EDITOR::collectChildRuleNodes( int aParentId, std::vector<RULE_TREE_NODE*>& aResult )
1079{
1080 std::vector<RULE_TREE_NODE> children;
1081 getRuleTreeChildNodes( m_ruleTreeNodeDatas, aParentId, children );
1082
1083 for( const auto& child : children )
1084 {
1085 RULE_TREE_NODE* childNode = getRuleTreeNodeInfo( child.m_nodeId );
1086
1087 if( childNode->m_nodeType == RULE )
1088 aResult.push_back( childNode );
1089
1090 collectChildRuleNodes( childNode->m_nodeId, aResult );
1091 }
1092}
1093
1094
1095void DIALOG_DRC_RULE_EDITOR::collectModifiedRules( std::vector<RULE_TREE_NODE*>& aResult )
1096{
1097 for( RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
1098 {
1099 if( node.m_nodeType != RULE )
1100 continue;
1101
1102 if( node.m_nodeData && node.m_nodeData->IsNew() )
1103 {
1104 aResult.push_back( &node );
1105 continue;
1106 }
1107
1108 auto constraintData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1109
1110 if( constraintData && constraintData->WasEdited() )
1111 aResult.push_back( &node );
1112 }
1113}
1114
1115
1116bool DIALOG_DRC_RULE_EDITOR::validateAllRules( std::map<wxString, wxString>& aErrors )
1117{
1118 bool allValid = true;
1119
1120 // Track (ruleName, layerSource) pairs and their conditions to detect conflicts.
1121 // Rules with the same name and same layer scope must share the same condition to be
1122 // merged correctly. Rules with different layer scopes are saved as separate rules and
1123 // are allowed to have different conditions.
1124 std::map<std::pair<wxString, wxString>, std::set<wxString>> ruleConditions;
1125
1126 for( RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
1127 {
1128 if( node.m_nodeType != RULE )
1129 continue;
1130
1131 if( node.m_nodeData && node.m_nodeData->IsNew() )
1132 continue;
1133
1134 auto constraintData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1135
1136 if( constraintData )
1137 {
1138 // Individual constraint validation
1139 VALIDATION_RESULT result = constraintData->Validate();
1140
1141 if( !result.isValid )
1142 {
1143 wxString errorMsg;
1144
1145 for( const wxString& err : result.errors )
1146 {
1147 if( !errorMsg.IsEmpty() )
1148 errorMsg += wxS( "\n" );
1149
1150 errorMsg += err;
1151 }
1152
1153 aErrors[node.m_nodeName] = errorMsg;
1154 allValid = false;
1155 }
1156
1157 wxString ruleName = constraintData->GetRuleName();
1158 wxString condition = constraintData->GetRuleCondition();
1159 wxString layerSource = constraintData->GetLayerSource();
1160 ruleConditions[std::make_pair( ruleName, layerSource )].insert( condition );
1161 }
1162 }
1163
1164 // Check for same-name same-layer different-condition conflicts
1165 for( const auto& [key, conditions] : ruleConditions )
1166 {
1167 if( conditions.size() > 1 )
1168 {
1169 wxString errorMsg = _( "Multiple rules with the same name have different conditions. "
1170 "Rules with the same name must have identical conditions to be merged." );
1171 aErrors[key.first] = errorMsg;
1172 allValid = false;
1173 }
1174 }
1175
1176 return allValid;
1177}
1178
1179
1181{
1182 std::vector<RULE_TREE_NODE*> modifiedRules;
1183 collectModifiedRules( modifiedRules );
1184
1185 if( modifiedRules.empty() )
1186 return wxID_NO;
1187
1188 wxString message = _( "The following rules have unsaved changes:\n\n" );
1189
1190 for( RULE_TREE_NODE* rule : modifiedRules )
1191 message += wxString::Format( wxS( " \u2022 %s\n" ), rule->m_nodeName );
1192
1193 message += _( "\nDo you want to save your changes?" );
1194
1195 int result = wxMessageBox( message, _( "Save Changes?" ),
1196 wxYES_NO | wxCANCEL | wxICON_QUESTION, this );
1197
1198 if( result == wxYES )
1199 return wxID_YES;
1200 else if( result == wxNO )
1201 return wxID_NO;
1202 else
1203 return wxID_CANCEL;
1204}
1205
1206
1208{
1209 // Find the tree item ID for this node
1210 wxTreeItemIdValue cookie;
1211 wxTreeItemId root = m_ruleTreeCtrl->GetRootItem();
1212
1213 std::function<wxTreeItemId( wxTreeItemId )> findItem = [&]( wxTreeItemId parent ) -> wxTreeItemId
1214 {
1215 wxTreeItemId item = m_ruleTreeCtrl->GetFirstChild( parent, cookie );
1216
1217 while( item.IsOk() )
1218 {
1219 RULE_TREE_ITEM_DATA* data =
1220 dynamic_cast<RULE_TREE_ITEM_DATA*>( m_ruleTreeCtrl->GetItemData( item ) );
1221
1222 if( data && data->GetNodeId() == aNodeId )
1223 return item;
1224
1225 wxTreeItemId found = findItem( item );
1226
1227 if( found.IsOk() )
1228 return found;
1229
1230 item = m_ruleTreeCtrl->GetNextSibling( item );
1231 }
1232
1233 return wxTreeItemId();
1234 };
1235
1236 wxTreeItemId itemId = findItem( root );
1237
1238 if( itemId.IsOk() )
1239 m_ruleTreeCtrl->SelectItem( itemId );
1240}
1241
1242
1244 const wxString& aName, const DRC_RULE_EDITOR_ITEM_TYPE& aNodeType, const std::optional<int>& aParentId,
1245 const std::optional<DRC_RULE_EDITOR_CONSTRAINT_NAME>& aConstraintType,
1246 const std::vector<RULE_TREE_NODE>& aChildNodes, const std::optional<int>& id )
1247{
1248 unsigned int newId;
1249
1250 if( id )
1251 {
1252 newId = *id; // Use provided ID
1253 }
1254 else
1255 {
1256 newId = 1;
1257
1258 if( m_nodeId )
1259 newId = m_nodeId + 1;
1260 }
1261
1262 m_nodeId = newId;
1263
1264 RULE_EDITOR_DATA_BASE baseData;
1265 baseData.SetId( newId );
1266
1267 if( aParentId )
1268 {
1269 baseData.SetParentId( *aParentId );
1270 }
1271
1272 return { .m_nodeId = m_nodeId,
1273 .m_nodeName = aName,
1274 .m_nodeType = aNodeType,
1275 .m_nodeLevel = -1,
1276 .m_nodeTypeMap = aConstraintType,
1277 .m_childNodes = aChildNodes,
1278 .m_nodeData = std::make_shared<RULE_EDITOR_DATA_BASE>( baseData ) };
1279}
1280
1281
1282RULE_TREE_NODE DIALOG_DRC_RULE_EDITOR::buildRuleNodeFromKicadDrc( const wxString& aName, const wxString& aCode,
1283 const std::optional<int>& aParentId )
1284{
1286 RULE_TREE_NODE node =
1288
1289 auto baseData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1290 DRC_RULE_EDITOR_UTILS::ConstraintFromKicadDrc( aCode, baseData.get() );
1291 node.m_nodeData = baseData;
1292 return node;
1293}
1294
1295
1297{
1298 return !m_cancelled;
1299}
1300
1301
1302void DIALOG_DRC_RULE_EDITOR::AdvancePhase( const wxString& aMessage )
1303{
1305 SetCurrentProgress( 0.0 );
1306}
1307
1308
1313
1315{
1316 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries;
1317
1318 for( const RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
1319 {
1320 if( node.m_nodeType != RULE )
1321 continue;
1322
1323 auto data = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1324
1325 if( !data )
1326 continue;
1327
1328 if( node.m_nodeData->IsNew() )
1329 continue;
1330
1332
1333 if( node.m_nodeTypeMap )
1334 entry.panelType = static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( *node.m_nodeTypeMap );
1335 else
1336 entry.panelType = CUSTOM_RULE;
1337
1338 entry.constraintData = data;
1339 entry.ruleName = data->GetRuleName();
1340 entry.condition = data->GetRuleCondition();
1341 entry.originalRuleText = data->GetOriginalRuleText();
1342 entry.wasEdited = data->WasEdited();
1343 entry.severity = data->GetSeverity();
1344 entry.layerCondition = LSET( data->GetLayers() );
1345 entry.layerSource = data->GetLayerSource();
1346
1347 entries.push_back( entry );
1348 }
1349
1350 DRC_RULE_SAVER saver;
1351 saver.SaveFile( m_frame->GetDesignRulesPath(), entries, m_currentBoard );
1352
1353 try
1354 {
1355 m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine(
1356 m_frame->GetDesignRulesPath() );
1357 }
1358 catch( PARSE_ERROR& pe )
1359 {
1360 wxLogError( _( "Failed to reload DRC rules: %s" ), pe.What() );
1361 }
1362}
static TOOL_ACTION zoomFitSelection
Definition actions.h:144
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:224
static TOOL_ACTION selectItems
Select a list of items (specified as the event parameter)
Definition actions.h:232
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
std::vector< RULE_TREE_NODE > m_ruleTreeNodeDatas
PANEL_DRC_GROUP_HEADER * m_groupHeaderPanel
bool validateAllRules(std::map< wxString, wxString > &aErrors)
Validates all rules and returns any that have validation errors.
std::vector< RULE_TREE_NODE > buildElectricalRuleTreeNodes(int &aParentId)
std::vector< RULE_TREE_NODE > buildHighspeedDesignRuleTreeNodes(int &aParentId)
void UpdateRuleTypeTreeItemData(RULE_TREE_ITEM_DATA *aCurrentRuleTreeItemData) override
Updates the rule tree item data by transferring data from the rule editor panel and updating the item...
void collectChildRuleNodes(int aParentId, std::vector< RULE_TREE_NODE * > &aResult)
Collects all child rule nodes for a given parent node ID.
int highlightMatchingItems(int aNodeId)
Highlights board items matching the current rule.
RULE_TREE_NODE buildRuleTreeNodeData(const wxString &aName, const DRC_RULE_EDITOR_ITEM_TYPE &aNodeType, const std::optional< int > &aParentId=std::nullopt, const std::optional< DRC_RULE_EDITOR_CONSTRAINT_NAME > &aConstraintType=std::nullopt, const std::vector< RULE_TREE_NODE > &aChildNodes={}, const std::optional< int > &aId=std::nullopt)
Creates a new rule tree node with the specified parameters, generating a new ID if not provided.
RULE_TREE_NODE buildRuleTreeNode(RULE_TREE_ITEM_DATA *aRuleTreeItemData, const wxString &aBaseName=wxEmptyString)
Creates a new rule tree node with a unique name and assigns the appropriate constraint data.
void saveRule(int aNodeId)
Saves the rule after validating the rule editor panel.
void AddNewRule(RULE_TREE_ITEM_DATA *aRuleTreeItemData) override
Adds a new rule to the rule tree, either as a child or under the parent, based on the node type (CONS...
void RuleTreeItemSelectionChanged(RULE_TREE_ITEM_DATA *aCurrentRuleTreeItemData) override
Handles rule tree item selection changes, updating the content panel with appropriate editor or heade...
int promptUnsavedChanges()
Shows a prompt for unsaved changes when closing with modifications.
RULE_TREE_NODE buildRuleNodeFromKicadDrc(const wxString &aName, const wxString &aCode, const std::optional< int > &aParentId=std::nullopt)
Build a rule tree node from a constraint keyword loaded from a .kicad_drc file.
std::vector< RULE_TREE_NODE > GetDefaultRuleTreeItems() override
Pure virtual method to get the default rule tree items.
std::shared_ptr< RC_ITEMS_PROVIDER > m_markersProvider
std::vector< RULE_TREE_NODE > buildManufacturabilityRuleTreeNodes(int &aParentId)
void RemoveRule(int aNodeId) override
Removes a rule from the rule tree after confirmation, deleting the item and associated data.
void OnCancel(wxCommandEvent &aEvent) override
std::vector< RULE_TREE_NODE > buildFootprintsRuleTreeNodes(int &aParentId)
RULE_TREE_NODE * getRuleTreeNodeInfo(const int &aNodeId)
Retrieves the rule tree node for a given ID.
void selectRuleNode(int aNodeId)
Selects a rule node in the tree by its ID.
bool validateRuleName(int aNodeId, const wxString &aRuleName)
Validates if the rule name is unique for the given node ID.
PANEL_DRC_RULE_EDITOR * m_ruleEditorPanel
void collectModifiedRules(std::vector< RULE_TREE_NODE * > &aResult)
Collects all rule nodes that have unsaved changes (new or edited).
bool deleteTreeNodeData(const int &aNodeId)
Deletes a rule tree node by its ID.
bool isEnabled(RULE_TREE_ITEM_DATA *aRuleTreeItemData, RULE_EDITOR_TREE_CONTEXT_OPT aOption) override
Verifies if a context menu option should be enabled based on the rule tree item type.
void closeRuleEntryView(int aNodeId)
Closes the rule entry view and re-enables controls.
void OnSave(wxCommandEvent &aEvent) override
void DuplicateRule(RULE_TREE_ITEM_DATA *aRuleTreeItemData) override
Duplicates a rule from the source tree node and appends it as a new item under the same parent.
wxSize m_initialSize
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
static bool IsNumericInputType(const DRC_RULE_EDITOR_CONSTRAINT_NAME &aConstraintType)
static wxString ConstraintToKicadDrc(DRC_RULE_EDITOR_CONSTRAINT_NAME aType)
Convert a constraint type into the keyword used in a .kicad_drc file.
static bool ConstraintFromKicadDrc(const wxString &aCode, DRC_RE_BASE_CONSTRAINT_DATA *aData)
Populate a constraint data object using a keyword from a .kicad_drc file.
static std::optional< DRC_RULE_EDITOR_CONSTRAINT_NAME > GetConstraintTypeFromCode(const wxString &aCode)
Resolve a constraint keyword from a rules file into the corresponding rule tree enumeration value.
static std::shared_ptr< DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA > CreateNumericConstraintData(DRC_RULE_EDITOR_CONSTRAINT_NAME aType)
static bool IsBoolInputType(const DRC_RULE_EDITOR_CONSTRAINT_NAME &aConstraintType)
Loads DRC rules from .kicad_dru files and converts them to panel entries.
std::vector< DRC_RE_LOADED_PANEL_ENTRY > LoadFile(const wxString &aPath)
Load all rules from a .kicad_dru file.
Saves DRC panel entries back to .kicad_dru files.
bool SaveFile(const wxString &aPath, const std::vector< DRC_RE_LOADED_PANEL_ENTRY > &aEntries, const BOARD *aBoard=nullptr)
Save all panel entries to a file.
virtual const wxString What() const
A composite of Problem() and Where()
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
@ MARKER_DRAWING_SHEET
Definition marker_base.h:56
The main frame for Pcbnew.
virtual void AdvancePhase() override
Use the next available virtual zone of the dialog progress bar.
virtual void SetCurrentProgress(double aProgress) override
Set the progress value to aProgress (0..1).
virtual void AdvancePhase()=0
Use the next available virtual zone of the dialog progress bar.
Concrete class representing the base data structure for a rule editor.
bool IsNew()
Check if the rule is marked as new.
int GetId()
Get the unique ID of the rule.
void SetIsNew(bool aIsNew)
Mark the rule as new or not.
void SetId(int aId)
Set the unique ID of the rule.
void SetParentId(int aParentId)
Set the parent ID of the rule.
void CopyFrom(const ICopyable &aSource) override
Implementation of the polymorphic CopyFrom method.
wxString GetRuleName()
Get the name of the rule.
void DeleteRuleTreeItem(wxTreeItemId aItemId, const int &aNodeId)
Deletes a tree item and removes its corresponding node from history.
void UpdateRuleTreeItemText(wxTreeItemId aItemId, wxString aItemText)
Updates the text of a specified rule tree item.
void SetModified()
Marks the dialog as modified, indicating unsaved changes.
void AppendNewRuleTreeItem(const RULE_TREE_NODE &aRuleTreeNode, wxTreeItemId aParentTreeItemId)
Adds a new rule tree item under the specified parent and updates the tree history.
void InitRuleTreeItems(const std::vector< RULE_TREE_NODE > &aRuleTreeNodes)
Initializes the rule tree by adding nodes, setting up the structure, and saving its state.
void SetContentPanel(wxPanel *aContentPanel)
Replaces the current content panel with a new one based on the selected constraint type.
wxScrolledWindow * m_scrolledContentWin
void ClearModified()
Clears the modified flag, typically after saving.
RULE_EDITOR_DIALOG_BASE(wxWindow *aParent, const wxString &aTitle, const wxSize &aInitialSize=wxDefaultSize)
std::unordered_map< int, std::tuple< wxString, std::vector< int >, wxTreeItemId > > m_treeHistoryData
bool IsModified() const
Returns whether the dialog has unsaved changes.
void getRuleTreeChildNodes(const std::vector< RULE_TREE_NODE > &aNodes, int aParentId, std::vector< RULE_TREE_NODE > &aResult)
Retrieves child nodes of a given parent node.
RULE_TREE_ITEM_DATA * GetCurrentlySelectedRuleTreeItemData()
Retrieves the currently selected rule tree item data.
void SetControlsEnabled(bool aEnable)
Enables or disables controls within the rule editor dialog.
A class representing additional data associated with a wxTree item.
wxTreeItemId GetTreeItemId() const
wxTreeItemId GetParentTreeItemId() const
int OKOrCancelDialog(wxWindow *aParent, const wxString &aWarning, const wxString &aMessage, const wxString &aDetailedMessage, const wxString &aOKLabel, const wxString &aCancelLabel, bool *aApplyToAll)
Display a warning dialog with aMessage and returns the user response.
Definition confirm.cpp:150
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:202
This file is part of the common library.
bool nodeExists(const RULE_TREE_NODE &aRuleTreeNode, const wxString &aTargetName)
Checks if a node with the given name exists in the rule tree or its child nodes.
const RULE_TREE_NODE * FindNodeById(const std::vector< RULE_TREE_NODE > &aNodes, int aTargetId)
#define DIALOG_DRC_RULE_EDITOR_WINDOW_NAME
SIM_MODEL::PARAM::CATEGORY CATEGORY
DRC_RULE_EDITOR_CONSTRAINT_NAME
@ ALLOWED_ORIENTATION
@ SILK_TO_SILK_CLEARANCE
@ ROUTING_DIFF_PAIR
@ SOLDERPASTE_EXPANSION
@ SILK_TO_SOLDERMASK_CLEARANCE
@ COURTYARD_CLEARANCE
@ MINIMUM_CONNECTION_WIDTH
@ SOLDERMASK_EXPANSION
@ MAXIMUM_VIA_COUNT
@ MINIMUM_ANNULAR_WIDTH
@ PHYSICAL_CLEARANCE
@ CREEPAGE_DISTANCE
@ ABSOLUTE_LENGTH
@ MINIMUM_CLEARANCE
@ MINIMUM_THERMAL_RELIEF_SPOKE_COUNT
@ PERMITTED_LAYERS
@ MINIMUM_TEXT_HEIGHT_AND_THICKNESS
@ COPPER_TO_HOLE_CLEARANCE
@ HOLE_TO_HOLE_DISTANCE
@ MINIMUM_DRILL_SIZE
@ MATCHED_LENGTH_DIFF_PAIR
@ COPPER_TO_EDGE_CLEARANCE
@ MINIMUM_SOLDERMASK_SLIVER
DRC_RULE_EDITOR_ITEM_TYPE
#define _(s)
RULE_EDITOR_TREE_CONTEXT_OPT
Enumeration representing the available context menu options for the rule editor tree.
Represents a rule loaded from a .kicad_dru file and mapped to a panel.
wxString ruleName
wxString originalRuleText
wxString condition
bool wasEdited
wxString layerSource
Original layer text: "inner", "outer", or layer name.
LSET layerCondition
std::shared_ptr< DRC_RE_BASE_CONSTRAINT_DATA > constraintData
DRC_RULE_EDITOR_CONSTRAINT_NAME panelType
SEVERITY severity
A filename or source description, a problem input line, a line number, a byte offset,...
Structure representing a node in a rule tree, collection of this used for building the rule tree.
std::shared_ptr< RULE_EDITOR_DATA_BASE > m_nodeData
std::optional< int > m_nodeTypeMap
std::vector< RULE_TREE_NODE > m_childNodes
Result of a validation operation.
wxString result
Test unit parsing edge cases and error handling.
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:88
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:108
@ PCB_NETINFO_T
class NETINFO_ITEM, a description of a net
Definition typeinfo.h:107