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 if( !m_frame->GetBoard() )
203 return;
204
205 wxFileName rulesFile( m_frame->GetBoard()->GetDesignRulesPath() );
206
207 if( !rulesFile.FileExists() )
208 return;
209
210 DRC_RULE_LOADER loader;
211 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFile( rulesFile.GetFullPath() );
212
213 if( entries.empty() )
214 return;
215
216 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
217 wxS( "[LoadExistingRules] Loaded %zu entries from %s" ),
218 entries.size(), rulesFile.GetFullPath() );
219
220 // Build lookup maps before the loading loop to avoid O(n) scans per rule.
221 // constraintTypeToNodeId maps panel type → parent node ID (from m_ruleTreeNodeDatas).
222 // m_treeHistoryData already maps node ID → wxTreeItemId (populated by InitRuleTreeItems).
223 std::unordered_map<int, int> constraintTypeToNodeId;
224
225 for( const RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
226 {
227 if( node.m_nodeType == CONSTRAINT && node.m_nodeTypeMap )
228 {
229 constraintTypeToNodeId[*node.m_nodeTypeMap] = node.m_nodeId;
230
231 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
232 wxS( "[LoadExistingRules] Node '%s': nodeId=%d, m_nodeTypeMap=%d" ),
233 wxString( node.m_nodeName ), node.m_nodeId,
234 static_cast<int>( *node.m_nodeTypeMap ) );
235 }
236 }
237
238 // Suppress selection-change events and repaint during bulk loading. Without this,
239 // each AppendNewRuleTreeItem triggers SelectItem which creates and immediately
240 // destroys a full PANEL_DRC_RULE_EDITOR (regex compilation, Scintilla, layout)
241 // for every rule. On Windows this causes >11s load times for moderate rule sets.
242 m_ruleTreeCtrl->Freeze();
244
245 for( DRC_RE_LOADED_PANEL_ENTRY& entry : entries )
246 {
247 DRC_RULE_EDITOR_CONSTRAINT_NAME type = entry.panelType;
248
249 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
250 wxS( "[LoadExistingRules] Processing entry: rule='%s', panelType=%d" ),
251 entry.ruleName, static_cast<int>( type ) );
252
253 auto typeIt = constraintTypeToNodeId.find( static_cast<int>( type ) );
254
255 if( typeIt == constraintTypeToNodeId.end() )
256 {
257 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
258 wxS( "[LoadExistingRules] No parent found for panelType=%d, skipping" ),
259 static_cast<int>( type ) );
260 continue;
261 }
262
263 int parentId = typeIt->second;
264
265 auto histIt = m_treeHistoryData.find( parentId );
266
267 if( histIt == m_treeHistoryData.end() )
268 {
269 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
270 wxS( "[LoadExistingRules] Tree item not found for parentId=%d, skipping" ),
271 parentId );
272 continue;
273 }
274
275 wxTreeItemId parentItem = std::get<2>( histIt->second );
276
277 if( !parentItem.IsOk() )
278 {
279 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
280 wxS( "[LoadExistingRules] Tree item not valid for parentId=%d, skipping" ),
281 parentId );
282 continue;
283 }
284
285 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
286 wxS( "[LoadExistingRules] Found parent node: parentId=%d" ), parentId );
287
288 RULE_TREE_NODE node =
289 buildRuleTreeNodeData( entry.ruleName, RULE, parentId, type );
290
291 auto ruleData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( entry.constraintData );
292
293 if( ruleData )
294 {
295 ruleData->SetId( node.m_nodeData->GetId() );
296 ruleData->SetParentId( parentId );
297 ruleData->SetOriginalRuleText( entry.originalRuleText );
298 ruleData->SetWasEdited( entry.wasEdited );
299 ruleData->SetLayerSource( entry.layerSource );
300
301 if( !entry.layerSource.IsEmpty() )
302 ruleData->SetLayers( entry.layerCondition.Seq() );
303
304 ruleData->SetSeverity( entry.severity );
305 node.m_nodeData = ruleData;
306 }
307
308 m_ruleTreeNodeDatas.push_back( node );
309 AppendNewRuleTreeItem( node, parentItem );
310
311 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
312 wxS( "[LoadExistingRules] Appended rule '%s' to tree under parentId=%d" ),
313 entry.ruleName, parentId );
314 }
315
317 m_ruleTreeCtrl->Thaw();
318}
319
320
322{
323 wxTreeItemId treeItemId;
324 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( aRuleTreeItemData->GetNodeId() );
325
326 if( nodeDetail->m_nodeType == CONSTRAINT )
327 {
328 treeItemId = aRuleTreeItemData->GetTreeItemId();
329 }
330 else
331 {
332 treeItemId = aRuleTreeItemData->GetParentTreeItemId();
333 }
334
335 AppendNewRuleTreeItem( buildRuleTreeNode( aRuleTreeItemData ), treeItemId );
336 SetModified();
337}
338
339
341{
342 RULE_TREE_NODE* sourceTreeNode = getRuleTreeNodeInfo( aRuleTreeItemData->GetNodeId() );
343
344 auto sourceDataPtr = dynamic_pointer_cast<RULE_EDITOR_DATA_BASE>( sourceTreeNode->m_nodeData );
345
346 if( !sourceDataPtr )
347 return;
348
349 // Strip any trailing " <number>" suffix so the number increments
350 wxString baseName = sourceDataPtr->GetRuleName();
351 int lastSpace = baseName.Find( ' ', true );
352
353 if( lastSpace != wxNOT_FOUND )
354 {
355 wxString suffix = baseName.Mid( lastSpace + 1 );
356 long num;
357
358 if( suffix.ToLong( &num ) )
359 baseName = baseName.Left( lastSpace );
360 }
361
362 RULE_TREE_NODE targetTreeNode = buildRuleTreeNode( aRuleTreeItemData, baseName );
363 targetTreeNode.m_nodeData->CopyFrom( *sourceDataPtr );
364
365 wxTreeItemId treeItemId = aRuleTreeItemData->GetParentTreeItemId();
366 AppendNewRuleTreeItem( targetTreeNode, treeItemId );
367 SetModified();
368}
369
370
372{
373 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( aCurrentRuleTreeItemData->GetNodeId() );
374
375 // Freeze so panel creation and data population happen off-screen.
376 m_scrolledContentWin->Freeze();
377
378 if( nodeDetail->m_nodeType == ROOT || nodeDetail->m_nodeType == CATEGORY || nodeDetail->m_nodeType == CONSTRAINT )
379 {
380 std::vector<RULE_TREE_NODE*> ruleNodes;
381 collectChildRuleNodes( nodeDetail->m_nodeId, ruleNodes );
382
383 std::vector<DRC_RULE_ROW> rows;
384 rows.reserve( ruleNodes.size() );
385
386 for( RULE_TREE_NODE* ruleNode : ruleNodes )
387 {
388 RULE_TREE_NODE* parentNode = getRuleTreeNodeInfo( ruleNode->m_nodeData->GetParentId() );
389 wxString type = parentNode ? parentNode->m_nodeName : wxString{};
390 rows.push_back( { type, ruleNode->m_nodeData->GetRuleName(), ruleNode->m_nodeData->GetComment() } );
391 }
392
395 m_ruleEditorPanel = nullptr;
396 }
397 else if( nodeDetail->m_nodeType == RULE )
398 {
399 RULE_TREE_ITEM_DATA* parentItemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
400 m_ruleTreeCtrl->GetItemData( aCurrentRuleTreeItemData->GetParentTreeItemId() ) );
401 RULE_TREE_NODE* paretNodeDetail = getRuleTreeNodeInfo( parentItemData->GetNodeId() );
402 wxString constraintName = paretNodeDetail->m_nodeName;
403
405 m_scrolledContentWin, m_frame->GetBoard(),
406 static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( nodeDetail->m_nodeTypeMap.value_or( -1 ) ),
407 &constraintName, dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( nodeDetail->m_nodeData ) );
408
410 m_ruleEditorPanel->TransferDataToWindow();
411
412 m_ruleEditorPanel->SetSaveCallback(
413 [this]( int aNodeId )
414 {
415 this->saveRule( aNodeId );
416 } );
417
418 m_ruleEditorPanel->SetRemoveCallback(
419 [this]( int aNodeId )
420 {
421 this->RemoveRule( aNodeId );
422 } );
423
424 m_ruleEditorPanel->SetCloseCallback(
425 [this]( int aNodeId )
426 {
427 this->closeRuleEntryView( aNodeId );
428 } );
429
430 m_ruleEditorPanel->SetRuleNameValidationCallback(
431 [this]( int aNodeId, wxString aRuleName )
432 {
433 return this->validateRuleName( aNodeId, aRuleName );
434 } );
435
436 m_ruleEditorPanel->SetShowMatchesCallBack(
437 [this]( int aNodeId ) -> int
438 {
439 return this->highlightMatchingItems( aNodeId );
440 } );
441
442 m_groupHeaderPanel = nullptr;
443 }
444
445 m_scrolledContentWin->Thaw();
446}
447
448
449void DIALOG_DRC_RULE_EDITOR::OnSave( wxCommandEvent& aEvent )
450{
452 m_ruleEditorPanel->Save( aEvent );
453}
454
455
456void DIALOG_DRC_RULE_EDITOR::OnCancel( wxCommandEvent& aEvent )
457{
458 // If currently editing a panel, cancel that first
460 {
461 auto data = m_ruleEditorPanel->GetConstraintData();
462 bool isNew = data && data->IsNew();
463
464 m_ruleEditorPanel->Cancel( aEvent );
465
466 if( isNew )
467 {
468 // After canceling a new rule, check if there are any remaining modified rules
469 std::vector<RULE_TREE_NODE*> modifiedRules;
470 collectModifiedRules( modifiedRules );
471
472 if( modifiedRules.empty() )
474
475 return;
476 }
477 }
478
479 // If there are unsaved changes, prompt the user
480 if( IsModified() )
481 {
483
484 if( result == wxID_CANCEL )
485 return;
486
487 if( result == wxID_YES )
488 {
489 // Validate all rules before saving
490 std::map<wxString, wxString> errors;
491
492 if( !validateAllRules( errors ) )
493 {
494 // Find the first rule with an error and select it
496 {
497 if( errors.find( node.m_nodeName ) != errors.end() )
498 {
499 selectRuleNode( node.m_nodeId );
500
501 wxString msg = wxString::Format(
502 _( "Cannot save due to validation errors in rule '%s':\n\n%s" ),
503 node.m_nodeName, errors[node.m_nodeName] );
504 DisplayErrorMessage( this, msg );
505 return;
506 }
507 }
508
509 return;
510 }
511
514 }
515 }
516
517 // Purge unsaved new rules from memory so they don't reappear on reopen
518 std::vector<int> newRuleNodeIds;
519
520 for( const RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
521 {
522 if( node.m_nodeType == RULE && node.m_nodeData && node.m_nodeData->IsNew() )
523 newRuleNodeIds.push_back( node.m_nodeId );
524 }
525
526 for( int nodeId : newRuleNodeIds )
527 {
528 auto it = m_treeHistoryData.find( nodeId );
529
530 if( it != m_treeHistoryData.end() )
531 DeleteRuleTreeItem( std::get<2>( it->second ), nodeId );
532
533 deleteTreeNodeData( nodeId );
534 }
535
536 aEvent.Skip();
537}
538
539
541{
542 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( aRuleTreeItemData->GetNodeId() );
543
545 {
546 m_ruleEditorPanel->TransferDataFromWindow();
547
548 nodeDetail->m_nodeName = nodeDetail->m_nodeData->GetRuleName();
549 nodeDetail->m_nodeData->SetIsNew( false );
550
551 // Mark as edited so the rule gets regenerated instead of using original text
552 auto constraintData =
553 std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( nodeDetail->m_nodeData );
554
555 if( constraintData )
556 constraintData->SetWasEdited( true );
557
558 UpdateRuleTreeItemText( aRuleTreeItemData->GetTreeItemId(), nodeDetail->m_nodeName );
559 }
560}
561
562
581
582
584{
585 RULE_TREE_ITEM_DATA* itemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
586 m_ruleTreeCtrl->GetItemData( GetCurrentlySelectedRuleTreeItemData()->GetTreeItemId() ) );
587 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( itemData->GetNodeId() );
588
589 if( !nodeDetail->m_nodeData->IsNew() )
590 {
591 if( OKOrCancelDialog( this, _( "Confirmation" ), "", _( "Are you sure you want to delete?" ), _( "Delete" ) )
592 != wxID_OK )
593 {
594 return;
595 }
596 }
597
598 if( itemData )
599 {
600 int nodeId = itemData->GetNodeId();
601
602 SetModified();
603 DeleteRuleTreeItem( GetCurrentlySelectedRuleTreeItemData()->GetTreeItemId(), nodeId );
604 deleteTreeNodeData( nodeId );
607 }
608
609 SetControlsEnabled( true );
610}
611
612
613std::vector<RULE_TREE_NODE> DIALOG_DRC_RULE_EDITOR::buildElectricalRuleTreeNodes( int& aParentId )
614{
615 std::vector<RULE_TREE_NODE> result;
616 int lastParentId;
617
618 result.push_back( buildRuleTreeNodeData( "Clearance", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
619 lastParentId = m_nodeId;
620
621 result.push_back( buildRuleTreeNodeData( "Minimum clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
623 result.push_back( buildRuleTreeNodeData( "Copper to edge clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
624 lastParentId, COPPER_TO_EDGE_CLEARANCE ) );
625 result.push_back( buildRuleTreeNodeData( "Courtyard clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
627 result.push_back( buildRuleTreeNodeData( "Physical clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
629 result.push_back( buildRuleTreeNodeData( "Creepage distance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
631
632 result.push_back( buildRuleTreeNodeData( "Minimum connection width", CONSTRAINT, aParentId,
634 result.push_back( buildRuleTreeNodeData( "Copper to hole clearance", CONSTRAINT, aParentId,
636 result.push_back( buildRuleTreeNodeData( "Minimum thermal relief spoke count", CONSTRAINT, aParentId,
638
639 return result;
640}
641
642
643std::vector<RULE_TREE_NODE> DIALOG_DRC_RULE_EDITOR::buildManufacturabilityRuleTreeNodes( int& aParentId )
644{
645 std::vector<RULE_TREE_NODE> result;
646 int lastParentId;
647
648 result.push_back( buildRuleTreeNodeData( "Minimum annular width", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
649 aParentId, MINIMUM_ANNULAR_WIDTH ) );
650
651 result.push_back( buildRuleTreeNodeData( "Hole", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
652 lastParentId = m_nodeId;
654 lastParentId, MINIMUM_DRILL_SIZE ) );
655 result.push_back( buildRuleTreeNodeData( "Hole to hole distance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
656 lastParentId, HOLE_TO_HOLE_DISTANCE ) );
657 result.push_back(
659
660 result.push_back( buildRuleTreeNodeData( "Minimum text height and thickness", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
662
663 result.push_back( buildRuleTreeNodeData( "Silk to silk clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
664 aParentId, SILK_TO_SILK_CLEARANCE ) );
665 result.push_back( buildRuleTreeNodeData( "Silk to soldermask clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
666 aParentId, SILK_TO_SOLDERMASK_CLEARANCE ) );
667
668 result.push_back( buildRuleTreeNodeData( "Minimum soldermask sliver", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
669 aParentId, MINIMUM_SOLDERMASK_SLIVER ) );
670 result.push_back( buildRuleTreeNodeData( "Soldermask expansion", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
671 aParentId, SOLDERMASK_EXPANSION ) );
672
673 result.push_back( buildRuleTreeNodeData( "Solderpaste expansion", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
674 aParentId, SOLDERPASTE_EXPANSION ) );
675
676 return result;
677}
678
679
680std::vector<RULE_TREE_NODE> DIALOG_DRC_RULE_EDITOR::buildHighspeedDesignRuleTreeNodes( int& aParentId )
681{
682 std::vector<RULE_TREE_NODE> result;
683
684 result.push_back(
686 result.push_back( buildRuleTreeNodeData( "Maximum via count", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
688 result.push_back( buildRuleTreeNodeData( "Routing diff pair", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
690 result.push_back( buildRuleTreeNodeData( "Matched length diff pair", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
691 aParentId, MATCHED_LENGTH_DIFF_PAIR ) );
692 result.push_back( buildRuleTreeNodeData( "Absolute length", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
693 ABSOLUTE_LENGTH ) );
694
695 return result;
696}
697
698
699std::vector<RULE_TREE_NODE> DIALOG_DRC_RULE_EDITOR::buildFootprintsRuleTreeNodes( int& aParentId )
700{
701 std::vector<RULE_TREE_NODE> result;
702 result.push_back( buildRuleTreeNodeData( "Permitted layers", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
704 result.push_back( buildRuleTreeNodeData( "Allowed orientation", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
706 result.push_back( buildRuleTreeNodeData( "Vias under SMD", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
707 VIAS_UNDER_SMD ) );
708
709 return result;
710}
711
712
720bool nodeExists( const RULE_TREE_NODE& aRuleTreeNode, const wxString& aTargetName )
721{
722 if( aRuleTreeNode.m_nodeName == aTargetName )
723 {
724 return true;
725 }
726
727 for( const auto& child : aRuleTreeNode.m_childNodes )
728 {
729 if( nodeExists( child, aTargetName ) )
730 {
731 return true;
732 }
733 }
734
735 return false;
736}
737
738
746bool nodeExists( const std::vector<RULE_TREE_NODE>& aRuleTreeNodes, const wxString& aTargetName )
747{
748 for( const auto& node : aRuleTreeNodes )
749 {
750 if( nodeExists( node, aTargetName ) )
751 {
752 return true;
753 }
754 }
755
756 return false;
757}
758
759
761 const wxString& aBaseName )
762{
763 // Factory function type for creating constraint data objects
764 using ConstraintDataFactory =
765 std::function<std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA>( const DRC_RE_BASE_CONSTRAINT_DATA& )>;
766
767 // Factory map for constraint data creation
768 static const std::unordered_map<DRC_RULE_EDITOR_CONSTRAINT_NAME, ConstraintDataFactory> s_constraintFactories = {
770 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
771 {
772 return std::make_shared<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>( data );
773 } },
775 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
776 {
777 return std::make_shared<DRC_RE_MINIMUM_TEXT_HEIGHT_THICKNESS_CONSTRAINT_DATA>( data );
778 } },
780 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
781 {
782 return std::make_shared<DRC_RE_ROUTING_DIFF_PAIR_CONSTRAINT_DATA>( data );
783 } },
785 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
786 {
787 return std::make_shared<DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA>( data );
788 } },
790 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
791 {
792 return std::make_shared<DRC_RE_PERMITTED_LAYERS_CONSTRAINT_DATA>( data );
793 } },
795 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
796 {
797 return std::make_shared<DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA>( data );
798 } },
800 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
801 {
802 return std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>( data );
803 } },
805 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
806 {
807 return std::make_shared<DRC_RE_ABSOLUTE_LENGTH_TWO_CONSTRAINT_DATA>( data );
808 } },
810 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
811 {
812 return std::make_shared<DRC_RE_MATCHED_LENGTH_DIFF_PAIR_CONSTRAINT_DATA>( data );
813 } },
815 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
816 {
817 return std::make_shared<DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA>( data );
818 } }
819 };
820
821 RULE_TREE_ITEM_DATA* treeItemData;
822 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( aRuleTreeItemData->GetNodeId() );
823
824 if( nodeDetail->m_nodeType == CONSTRAINT )
825 {
826 treeItemData = aRuleTreeItemData;
827 }
828 else
829 {
830 treeItemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
831 m_ruleTreeCtrl->GetItemData( aRuleTreeItemData->GetParentTreeItemId() ) );
832 nodeDetail = getRuleTreeNodeInfo( treeItemData->GetNodeId() );
833 }
834
835 m_nodeId++;
836
837 wxString base = aBaseName.IsEmpty() ? nodeDetail->m_nodeName : aBaseName;
838 wxString nodeName = base + " 1";
839
840 int loop = 2;
841 bool check = false;
842
843 do
844 {
845 check = false;
846
847 if( nodeExists( m_ruleTreeNodeDatas, nodeName ) )
848 {
849 nodeName = base + wxString::Format( " %d", loop );
850 loop++;
851 check = true;
852 }
853 } while( check );
854
856 nodeName, RULE, nodeDetail->m_nodeId,
857 static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( nodeDetail->m_nodeTypeMap.value_or( 0 ) ), {}, m_nodeId );
858
859 auto nodeType = static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( newRuleNode.m_nodeTypeMap.value_or( -1 ) );
860
861 DRC_RE_BASE_CONSTRAINT_DATA clearanceData( m_nodeId, nodeDetail->m_nodeData->GetId(), newRuleNode.m_nodeName );
862
863 if( s_constraintFactories.find( nodeType ) != s_constraintFactories.end() )
864 {
865 newRuleNode.m_nodeData = s_constraintFactories.at( nodeType )( clearanceData );
866 }
867 else if( DRC_RULE_EDITOR_UTILS::IsNumericInputType( nodeType ) )
868 {
869 newRuleNode.m_nodeData = DRC_RULE_EDITOR_UTILS::CreateNumericConstraintData( nodeType, clearanceData );
870 }
871 else if( DRC_RULE_EDITOR_UTILS::IsBoolInputType( nodeType ) )
872 {
873 newRuleNode.m_nodeData = std::make_shared<DRC_RE_BOOL_INPUT_CONSTRAINT_DATA>( clearanceData );
874 }
875 else
876 {
877 wxLogWarning( "No factory found for constraint type: %d", nodeType );
878 newRuleNode.m_nodeData = std::make_shared<DRC_RE_BASE_CONSTRAINT_DATA>( clearanceData );
879 }
880
881 std::static_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( newRuleNode.m_nodeData )
882 ->SetConstraintCode( DRC_RULE_EDITOR_UTILS::ConstraintToKicadDrc( nodeType ) );
883 newRuleNode.m_nodeData->SetIsNew( true );
884
885 m_ruleTreeNodeDatas.push_back( newRuleNode );
886
887 return newRuleNode;
888}
889
890
892{
893 auto it = std::find_if( m_ruleTreeNodeDatas.begin(), m_ruleTreeNodeDatas.end(),
894 [aNodeId]( const RULE_TREE_NODE& node )
895 {
896 return node.m_nodeId == aNodeId;
897 } );
898
899 if( it != m_ruleTreeNodeDatas.end() )
900 {
901 return &( *it ); // Return pointer to the found node
902 }
903 else
904 return nullptr;
905}
906
907
909{
910 if( !m_ruleEditorPanel->GetIsValidationSucceeded() )
911 {
912 wxString validationMessage = m_ruleEditorPanel->GetValidationMessage();
913
914 DisplayErrorMessage( this, validationMessage );
915 }
916 else
917 {
918 RULE_TREE_ITEM_DATA* itemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
919 m_ruleTreeCtrl->GetItemData( GetCurrentlySelectedRuleTreeItemData()->GetTreeItemId() ) );
920
921 if( itemData )
922 {
923 UpdateRuleTypeTreeItemData( itemData );
924 }
925
928
929 SetControlsEnabled( true );
930 }
931}
932
933
935{
936 SetControlsEnabled( true );
937}
938
939
941{
942 (void) aNodeId;
943
944 if( !m_ruleEditorPanel )
945 return -1;
946
947 // Ensure we use the latest text from the condition editor
948 m_ruleEditorPanel->TransferDataFromWindow();
949
950 std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA> constraintData = m_ruleEditorPanel->GetConstraintData();
951 std::shared_ptr<DRC_RULE> selectedRule;
952 wxString condition;
953 wxString ruleText = constraintData->GetGeneratedRule();
954
955 if( ruleText.IsEmpty() )
956 {
957 m_frame->FocusOnItems( {} );
958 Raise();
959 return 0;
960 }
961
962 wxString fullText = wxS( "(version 2)\n" ) + ruleText;
963
964 try
965 {
966 std::vector<std::shared_ptr<DRC_RULE>> rules;
967 DRC_RULES_PARSER parser( fullText, wxS( "ShowMatches" ) );
968 parser.Parse( rules, nullptr );
969
970 if( rules.empty() )
971 {
972 m_frame->FocusOnItems( {} );
973 Raise();
974 return 0;
975 }
976
977 selectedRule = rules[0];
978 condition = selectedRule->m_Condition ? selectedRule->m_Condition->GetExpression() : wxString();
979
980 if( selectedRule->m_Condition && !selectedRule->m_Condition->GetExpression().IsEmpty()
981 && !selectedRule->m_Condition->Compile( nullptr ) )
982 {
983 return -1;
984 }
985 }
986 catch( PARSE_ERROR& )
987 {
988 return -1;
989 }
990
991 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ), wxS( "[ShowMatches] nodeId=%d, condition='%s'" ), aNodeId,
992 condition );
993
994 m_drcTool = m_frame->GetToolManager()->GetTool<DRC_TOOL>();
995
996 std::vector<BOARD_ITEM*> allMatches;
997
998 allMatches = m_drcTool->GetDRCEngine()->GetItemsMatchingRule( selectedRule, m_reporter );
999
1000 // Filter out items without visible geometry
1001 std::vector<BOARD_ITEM*> matches;
1002
1003 for( BOARD_ITEM* item : allMatches )
1004 {
1005 switch( item->Type() )
1006 {
1007 case PCB_NETINFO_T:
1008 case PCB_GENERATOR_T:
1009 case PCB_GROUP_T:
1010 continue;
1011
1012 default:
1013 matches.push_back( item );
1014 break;
1015 }
1016 }
1017
1018 int matchCount = static_cast<int>( matches.size() );
1019
1020 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ), wxS( "[ShowMatches] matched_count=%d (filtered from %zu)" ),
1021 matchCount, allMatches.size() );
1022
1023 // Clear any existing selection and select matched items
1024 m_frame->GetToolManager()->RunAction( ACTIONS::selectionClear );
1025
1026 if( matches.size() > 0 )
1027 {
1028 std::vector<EDA_ITEM*> selectItems;
1029
1030 for( BOARD_ITEM* item : matches )
1031 selectItems.push_back( item );
1032
1033 m_frame->GetToolManager()->RunAction( ACTIONS::selectItems, &selectItems );
1034 m_frame->GetToolManager()->RunAction( ACTIONS::zoomFitSelection );
1035 }
1036
1037 // Also brighten items to provide additional visual feedback
1038 m_frame->FocusOnItems( matches );
1039 Raise();
1040
1041 return matchCount;
1042}
1043
1044
1045bool DIALOG_DRC_RULE_EDITOR::validateRuleName( int aNodeId, const wxString& aRuleName )
1046{
1047 auto it = std::find_if( m_ruleTreeNodeDatas.begin(), m_ruleTreeNodeDatas.end(),
1048 [aNodeId, aRuleName]( const RULE_TREE_NODE& node )
1049 {
1050 return node.m_nodeName == aRuleName && node.m_nodeId != aNodeId
1051 && node.m_nodeType == RULE;
1052 } );
1053
1054 if( it != m_ruleTreeNodeDatas.end() )
1055 {
1056 return false;
1057 }
1058
1059 return true;
1060}
1061
1062
1064{
1065 size_t initial_size = m_ruleTreeNodeDatas.size();
1066
1067 m_ruleTreeNodeDatas.erase( std::remove_if( m_ruleTreeNodeDatas.begin(), m_ruleTreeNodeDatas.end(),
1068 [aNodeId]( const RULE_TREE_NODE& node )
1069 {
1070 return node.m_nodeId == aNodeId;
1071 } ),
1072 m_ruleTreeNodeDatas.end() );
1073
1074 if( m_ruleTreeNodeDatas.size() < initial_size )
1075 return true;
1076 else
1077 return false;
1078}
1079
1080
1081void DIALOG_DRC_RULE_EDITOR::collectChildRuleNodes( int aParentId, std::vector<RULE_TREE_NODE*>& aResult )
1082{
1083 std::vector<RULE_TREE_NODE> children;
1084 getRuleTreeChildNodes( m_ruleTreeNodeDatas, aParentId, children );
1085
1086 for( const auto& child : children )
1087 {
1088 RULE_TREE_NODE* childNode = getRuleTreeNodeInfo( child.m_nodeId );
1089
1090 if( childNode->m_nodeType == RULE )
1091 aResult.push_back( childNode );
1092
1093 collectChildRuleNodes( childNode->m_nodeId, aResult );
1094 }
1095}
1096
1097
1098void DIALOG_DRC_RULE_EDITOR::collectModifiedRules( std::vector<RULE_TREE_NODE*>& aResult )
1099{
1100 for( RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
1101 {
1102 if( node.m_nodeType != RULE )
1103 continue;
1104
1105 if( node.m_nodeData && node.m_nodeData->IsNew() )
1106 {
1107 aResult.push_back( &node );
1108 continue;
1109 }
1110
1111 auto constraintData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1112
1113 if( constraintData && constraintData->WasEdited() )
1114 aResult.push_back( &node );
1115 }
1116}
1117
1118
1119bool DIALOG_DRC_RULE_EDITOR::validateAllRules( std::map<wxString, wxString>& aErrors )
1120{
1121 bool allValid = true;
1122
1123 // Track (ruleName, layerSource) pairs and their conditions to detect conflicts.
1124 // Rules with the same name and same layer scope must share the same condition to be
1125 // merged correctly. Rules with different layer scopes are saved as separate rules and
1126 // are allowed to have different conditions.
1127 std::map<std::pair<wxString, wxString>, std::set<wxString>> ruleConditions;
1128
1129 for( RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
1130 {
1131 if( node.m_nodeType != RULE )
1132 continue;
1133
1134 if( node.m_nodeData && node.m_nodeData->IsNew() )
1135 continue;
1136
1137 auto constraintData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1138
1139 if( constraintData )
1140 {
1141 // Individual constraint validation
1142 VALIDATION_RESULT result = constraintData->Validate();
1143
1144 if( !result.isValid )
1145 {
1146 wxString errorMsg;
1147
1148 for( const wxString& err : result.errors )
1149 {
1150 if( !errorMsg.IsEmpty() )
1151 errorMsg += wxS( "\n" );
1152
1153 errorMsg += err;
1154 }
1155
1156 aErrors[node.m_nodeName] = errorMsg;
1157 allValid = false;
1158 }
1159
1160 wxString ruleName = constraintData->GetRuleName();
1161 wxString condition = constraintData->GetRuleCondition();
1162 wxString layerSource = constraintData->GetLayerSource();
1163 ruleConditions[std::make_pair( ruleName, layerSource )].insert( condition );
1164 }
1165 }
1166
1167 // Check for same-name same-layer different-condition conflicts
1168 for( const auto& [key, conditions] : ruleConditions )
1169 {
1170 if( conditions.size() > 1 )
1171 {
1172 wxString errorMsg = _( "Multiple rules with the same name have different conditions. "
1173 "Rules with the same name must have identical conditions to be merged." );
1174 aErrors[key.first] = errorMsg;
1175 allValid = false;
1176 }
1177 }
1178
1179 return allValid;
1180}
1181
1182
1184{
1185 std::vector<RULE_TREE_NODE*> modifiedRules;
1186 collectModifiedRules( modifiedRules );
1187
1188 if( modifiedRules.empty() )
1189 return wxID_NO;
1190
1191 wxString message = _( "The following rules have unsaved changes:\n\n" );
1192
1193 for( RULE_TREE_NODE* rule : modifiedRules )
1194 message += wxString::Format( wxS( " \u2022 %s\n" ), rule->m_nodeName );
1195
1196 message += _( "\nDo you want to save your changes?" );
1197
1198 int result = wxMessageBox( message, _( "Save Changes?" ),
1199 wxYES_NO | wxCANCEL | wxICON_QUESTION, this );
1200
1201 if( result == wxYES )
1202 return wxID_YES;
1203 else if( result == wxNO )
1204 return wxID_NO;
1205 else
1206 return wxID_CANCEL;
1207}
1208
1209
1211{
1212 // Find the tree item ID for this node
1213 wxTreeItemIdValue cookie;
1214 wxTreeItemId root = m_ruleTreeCtrl->GetRootItem();
1215
1216 std::function<wxTreeItemId( wxTreeItemId )> findItem = [&]( wxTreeItemId parent ) -> wxTreeItemId
1217 {
1218 wxTreeItemId item = m_ruleTreeCtrl->GetFirstChild( parent, cookie );
1219
1220 while( item.IsOk() )
1221 {
1222 RULE_TREE_ITEM_DATA* data =
1223 dynamic_cast<RULE_TREE_ITEM_DATA*>( m_ruleTreeCtrl->GetItemData( item ) );
1224
1225 if( data && data->GetNodeId() == aNodeId )
1226 return item;
1227
1228 wxTreeItemId found = findItem( item );
1229
1230 if( found.IsOk() )
1231 return found;
1232
1233 item = m_ruleTreeCtrl->GetNextSibling( item );
1234 }
1235
1236 return wxTreeItemId();
1237 };
1238
1239 wxTreeItemId itemId = findItem( root );
1240
1241 if( itemId.IsOk() )
1242 m_ruleTreeCtrl->SelectItem( itemId );
1243}
1244
1245
1247 const wxString& aName, const DRC_RULE_EDITOR_ITEM_TYPE& aNodeType, const std::optional<int>& aParentId,
1248 const std::optional<DRC_RULE_EDITOR_CONSTRAINT_NAME>& aConstraintType,
1249 const std::vector<RULE_TREE_NODE>& aChildNodes, const std::optional<int>& id )
1250{
1251 unsigned int newId;
1252
1253 if( id )
1254 {
1255 newId = *id; // Use provided ID
1256 }
1257 else
1258 {
1259 newId = 1;
1260
1261 if( m_nodeId )
1262 newId = m_nodeId + 1;
1263 }
1264
1265 m_nodeId = newId;
1266
1267 RULE_EDITOR_DATA_BASE baseData;
1268 baseData.SetId( newId );
1269
1270 if( aParentId )
1271 {
1272 baseData.SetParentId( *aParentId );
1273 }
1274
1275 return { .m_nodeId = m_nodeId,
1276 .m_nodeName = aName,
1277 .m_nodeType = aNodeType,
1278 .m_nodeLevel = -1,
1279 .m_nodeTypeMap = aConstraintType,
1280 .m_childNodes = aChildNodes,
1281 .m_nodeData = std::make_shared<RULE_EDITOR_DATA_BASE>( baseData ) };
1282}
1283
1284
1285RULE_TREE_NODE DIALOG_DRC_RULE_EDITOR::buildRuleNodeFromKicadDrc( const wxString& aName, const wxString& aCode,
1286 const std::optional<int>& aParentId )
1287{
1289 RULE_TREE_NODE node =
1291
1292 auto baseData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1293 DRC_RULE_EDITOR_UTILS::ConstraintFromKicadDrc( aCode, baseData.get() );
1294 node.m_nodeData = baseData;
1295 return node;
1296}
1297
1298
1300{
1301 return !m_cancelled;
1302}
1303
1304
1305void DIALOG_DRC_RULE_EDITOR::AdvancePhase( const wxString& aMessage )
1306{
1308 SetCurrentProgress( 0.0 );
1309}
1310
1311
1316
1318{
1319 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries;
1320
1321 for( const RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
1322 {
1323 if( node.m_nodeType != RULE )
1324 continue;
1325
1326 auto data = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1327
1328 if( !data )
1329 continue;
1330
1331 if( node.m_nodeData->IsNew() )
1332 continue;
1333
1335
1336 if( node.m_nodeTypeMap )
1337 entry.panelType = static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( *node.m_nodeTypeMap );
1338 else
1339 entry.panelType = CUSTOM_RULE;
1340
1341 entry.constraintData = data;
1342 entry.ruleName = data->GetRuleName();
1343 entry.condition = data->GetRuleCondition();
1344 entry.originalRuleText = data->GetOriginalRuleText();
1345 entry.wasEdited = data->WasEdited();
1346 entry.severity = data->GetSeverity();
1347 entry.layerCondition = LSET( data->GetLayers() );
1348 entry.layerSource = data->GetLayerSource();
1349
1350 entries.push_back( entry );
1351 }
1352
1353 DRC_RULE_SAVER saver;
1354 saver.SaveFile( m_frame->GetBoard()->GetDesignRulesPath(), entries, m_currentBoard );
1355
1356 try
1357 {
1358 m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine( m_frame->GetBoard()->GetDesignRulesPath() );
1359 }
1360 catch( PARSE_ERROR& pe )
1361 {
1362 wxLogError( _( "Failed to reload DRC rules: %s" ), pe.What() );
1363 }
1364}
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:169
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:221
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