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
17 * along with this program. If not, see <https://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( "Microvia", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
661 lastParentId = m_nodeId;
662 result.push_back( buildRuleTreeNodeData( "Maximum stack depth", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
664 result.push_back( buildRuleTreeNodeData( "Maximum aspect ratio", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
665 lastParentId, MICROVIA_ASPECT_RATIO ) );
666
667 result.push_back( buildRuleTreeNodeData( "Minimum text height and thickness", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
669
670 result.push_back( buildRuleTreeNodeData( "Silk to silk clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
671 aParentId, SILK_TO_SILK_CLEARANCE ) );
672 result.push_back( buildRuleTreeNodeData( "Silk to soldermask clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
673 aParentId, SILK_TO_SOLDERMASK_CLEARANCE ) );
674
675 result.push_back( buildRuleTreeNodeData( "Minimum soldermask sliver", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
676 aParentId, MINIMUM_SOLDERMASK_SLIVER ) );
677 result.push_back( buildRuleTreeNodeData( "Soldermask expansion", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
678 aParentId, SOLDERMASK_EXPANSION ) );
679
680 result.push_back( buildRuleTreeNodeData( "Solderpaste expansion", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
681 aParentId, SOLDERPASTE_EXPANSION ) );
682
683 return result;
684}
685
686
687std::vector<RULE_TREE_NODE> DIALOG_DRC_RULE_EDITOR::buildHighspeedDesignRuleTreeNodes( int& aParentId )
688{
689 std::vector<RULE_TREE_NODE> result;
690
691 result.push_back(
693 result.push_back( buildRuleTreeNodeData( "Maximum via count", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
695 result.push_back( buildRuleTreeNodeData( "Routing diff pair", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
697 result.push_back( buildRuleTreeNodeData( "Matched length diff pair", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
698 aParentId, MATCHED_LENGTH_DIFF_PAIR ) );
699 result.push_back( buildRuleTreeNodeData( "Absolute length", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
700 ABSOLUTE_LENGTH ) );
701
702 return result;
703}
704
705
706std::vector<RULE_TREE_NODE> DIALOG_DRC_RULE_EDITOR::buildFootprintsRuleTreeNodes( int& aParentId )
707{
708 std::vector<RULE_TREE_NODE> result;
709 result.push_back( buildRuleTreeNodeData( "Permitted layers", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
711 result.push_back( buildRuleTreeNodeData( "Allowed orientation", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
713 result.push_back( buildRuleTreeNodeData( "Vias under SMD", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, aParentId,
714 VIAS_UNDER_SMD ) );
715
716 return result;
717}
718
719
727bool nodeExists( const RULE_TREE_NODE& aRuleTreeNode, const wxString& aTargetName )
728{
729 if( aRuleTreeNode.m_nodeName == aTargetName )
730 {
731 return true;
732 }
733
734 for( const auto& child : aRuleTreeNode.m_childNodes )
735 {
736 if( nodeExists( child, aTargetName ) )
737 {
738 return true;
739 }
740 }
741
742 return false;
743}
744
745
753bool nodeExists( const std::vector<RULE_TREE_NODE>& aRuleTreeNodes, const wxString& aTargetName )
754{
755 for( const auto& node : aRuleTreeNodes )
756 {
757 if( nodeExists( node, aTargetName ) )
758 {
759 return true;
760 }
761 }
762
763 return false;
764}
765
766
768 const wxString& aBaseName )
769{
770 // Factory function type for creating constraint data objects
771 using ConstraintDataFactory =
772 std::function<std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA>( const DRC_RE_BASE_CONSTRAINT_DATA& )>;
773
774 // Factory map for constraint data creation
775 static const std::unordered_map<DRC_RULE_EDITOR_CONSTRAINT_NAME, ConstraintDataFactory> s_constraintFactories = {
777 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
778 {
779 return std::make_shared<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>( data );
780 } },
782 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
783 {
784 return std::make_shared<DRC_RE_MINIMUM_TEXT_HEIGHT_THICKNESS_CONSTRAINT_DATA>( data );
785 } },
787 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
788 {
789 return std::make_shared<DRC_RE_ROUTING_DIFF_PAIR_CONSTRAINT_DATA>( data );
790 } },
792 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
793 {
794 return std::make_shared<DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA>( data );
795 } },
797 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
798 {
799 return std::make_shared<DRC_RE_PERMITTED_LAYERS_CONSTRAINT_DATA>( data );
800 } },
802 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
803 {
804 return std::make_shared<DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA>( data );
805 } },
807 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
808 {
809 return std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>( data );
810 } },
812 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
813 {
814 return std::make_shared<DRC_RE_ABSOLUTE_LENGTH_TWO_CONSTRAINT_DATA>( data );
815 } },
817 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
818 {
819 return std::make_shared<DRC_RE_MATCHED_LENGTH_DIFF_PAIR_CONSTRAINT_DATA>( data );
820 } },
822 []( const DRC_RE_BASE_CONSTRAINT_DATA& data )
823 {
824 return std::make_shared<DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA>( data );
825 } }
826 };
827
828 RULE_TREE_ITEM_DATA* treeItemData;
829 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( aRuleTreeItemData->GetNodeId() );
830
831 if( nodeDetail->m_nodeType == CONSTRAINT )
832 {
833 treeItemData = aRuleTreeItemData;
834 }
835 else
836 {
837 treeItemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
838 m_ruleTreeCtrl->GetItemData( aRuleTreeItemData->GetParentTreeItemId() ) );
839 nodeDetail = getRuleTreeNodeInfo( treeItemData->GetNodeId() );
840 }
841
842 m_nodeId++;
843
844 wxString base = aBaseName.IsEmpty() ? nodeDetail->m_nodeName : aBaseName;
845 wxString nodeName = base + " 1";
846
847 int loop = 2;
848 bool check = false;
849
850 do
851 {
852 check = false;
853
855 {
856 nodeName = base + wxString::Format( " %d", loop );
857 loop++;
858 check = true;
859 }
860 } while( check );
861
863 nodeName, RULE, nodeDetail->m_nodeId,
864 static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( nodeDetail->m_nodeTypeMap.value_or( 0 ) ), {}, m_nodeId );
865
866 auto nodeType = static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( newRuleNode.m_nodeTypeMap.value_or( -1 ) );
867
868 DRC_RE_BASE_CONSTRAINT_DATA clearanceData( m_nodeId, nodeDetail->m_nodeData->GetId(), newRuleNode.m_nodeName );
869
870 if( s_constraintFactories.find( nodeType ) != s_constraintFactories.end() )
871 {
872 newRuleNode.m_nodeData = s_constraintFactories.at( nodeType )( clearanceData );
873 }
874 else if( DRC_RULE_EDITOR_UTILS::IsNumericInputType( nodeType ) )
875 {
876 newRuleNode.m_nodeData = DRC_RULE_EDITOR_UTILS::CreateNumericConstraintData( nodeType, clearanceData );
877 }
878 else if( DRC_RULE_EDITOR_UTILS::IsBoolInputType( nodeType ) )
879 {
880 newRuleNode.m_nodeData = std::make_shared<DRC_RE_BOOL_INPUT_CONSTRAINT_DATA>( clearanceData );
881 }
882 else
883 {
884 wxLogWarning( "No factory found for constraint type: %d", nodeType );
885 newRuleNode.m_nodeData = std::make_shared<DRC_RE_BASE_CONSTRAINT_DATA>( clearanceData );
886 }
887
888 std::static_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( newRuleNode.m_nodeData )
889 ->SetConstraintCode( DRC_RULE_EDITOR_UTILS::ConstraintToKicadDrc( nodeType ) );
890 newRuleNode.m_nodeData->SetIsNew( true );
891
892 m_ruleTreeNodeDatas.push_back( newRuleNode );
893
894 return newRuleNode;
895}
896
897
899{
900 auto it = std::find_if( m_ruleTreeNodeDatas.begin(), m_ruleTreeNodeDatas.end(),
901 [aNodeId]( const RULE_TREE_NODE& node )
902 {
903 return node.m_nodeId == aNodeId;
904 } );
905
906 if( it != m_ruleTreeNodeDatas.end() )
907 {
908 return &( *it ); // Return pointer to the found node
909 }
910 else
911 return nullptr;
912}
913
914
916{
917 if( !m_ruleEditorPanel->GetIsValidationSucceeded() )
918 {
919 wxString validationMessage = m_ruleEditorPanel->GetValidationMessage();
920
921 DisplayErrorMessage( this, validationMessage );
922 }
923 else
924 {
925 RULE_TREE_ITEM_DATA* itemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
926 m_ruleTreeCtrl->GetItemData( GetCurrentlySelectedRuleTreeItemData()->GetTreeItemId() ) );
927
928 if( itemData )
929 {
930 UpdateRuleTypeTreeItemData( itemData );
931 }
932
935
936 SetControlsEnabled( true );
937 }
938}
939
940
942{
943 SetControlsEnabled( true );
944}
945
946
948{
949 (void) aNodeId;
950
951 if( !m_ruleEditorPanel )
952 return -1;
953
954 // Ensure we use the latest text from the condition editor
955 m_ruleEditorPanel->TransferDataFromWindow();
956
957 std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA> constraintData = m_ruleEditorPanel->GetConstraintData();
958 std::shared_ptr<DRC_RULE> selectedRule;
959 wxString condition;
960 wxString ruleText = constraintData->GetGeneratedRule();
961
962 if( ruleText.IsEmpty() )
963 {
964 m_frame->FocusOnItems( {} );
965 Raise();
966 return 0;
967 }
968
969 wxString fullText = wxS( "(version 2)\n" ) + ruleText;
970
971 try
972 {
973 std::vector<std::shared_ptr<DRC_RULE>> rules;
974 DRC_RULES_PARSER parser( fullText, wxS( "ShowMatches" ) );
975 parser.Parse( rules, nullptr );
976
977 if( rules.empty() )
978 {
979 m_frame->FocusOnItems( {} );
980 Raise();
981 return 0;
982 }
983
984 selectedRule = rules[0];
985 condition = selectedRule->m_Condition ? selectedRule->m_Condition->GetExpression() : wxString();
986
987 if( selectedRule->m_Condition && !selectedRule->m_Condition->GetExpression().IsEmpty()
988 && !selectedRule->m_Condition->Compile( nullptr ) )
989 {
990 return -1;
991 }
992 }
993 catch( PARSE_ERROR& )
994 {
995 return -1;
996 }
997
998 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ), wxS( "[ShowMatches] nodeId=%d, condition='%s'" ), aNodeId,
999 condition );
1000
1001 m_drcTool = m_frame->GetToolManager()->GetTool<DRC_TOOL>();
1002
1003 std::vector<BOARD_ITEM*> allMatches;
1004
1005 allMatches = m_drcTool->GetDRCEngine()->GetItemsMatchingRule( selectedRule, m_reporter );
1006
1007 // Filter out items without visible geometry
1008 std::vector<BOARD_ITEM*> matches;
1009
1010 for( BOARD_ITEM* item : allMatches )
1011 {
1012 switch( item->Type() )
1013 {
1014 case PCB_NETINFO_T:
1015 case PCB_GENERATOR_T:
1016 case PCB_GROUP_T:
1017 continue;
1018
1019 default:
1020 matches.push_back( item );
1021 break;
1022 }
1023 }
1024
1025 int matchCount = static_cast<int>( matches.size() );
1026
1027 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ), wxS( "[ShowMatches] matched_count=%d (filtered from %zu)" ),
1028 matchCount, allMatches.size() );
1029
1030 // Clear any existing selection and select matched items
1031 m_frame->GetToolManager()->RunAction( ACTIONS::selectionClear );
1032
1033 if( matches.size() > 0 )
1034 {
1035 std::vector<EDA_ITEM*> selectItems;
1036
1037 for( BOARD_ITEM* item : matches )
1038 selectItems.push_back( item );
1039
1040 m_frame->GetToolManager()->RunAction( ACTIONS::selectItems, &selectItems );
1041 m_frame->GetToolManager()->RunAction( ACTIONS::zoomFitSelection );
1042 }
1043
1044 // Also brighten items to provide additional visual feedback
1045 m_frame->FocusOnItems( matches );
1046 Raise();
1047
1048 return matchCount;
1049}
1050
1051
1052bool DIALOG_DRC_RULE_EDITOR::validateRuleName( int aNodeId, const wxString& aRuleName )
1053{
1054 auto it = std::find_if( m_ruleTreeNodeDatas.begin(), m_ruleTreeNodeDatas.end(),
1055 [aNodeId, aRuleName]( const RULE_TREE_NODE& node )
1056 {
1057 return node.m_nodeName == aRuleName && node.m_nodeId != aNodeId
1058 && node.m_nodeType == RULE;
1059 } );
1060
1061 if( it != m_ruleTreeNodeDatas.end() )
1062 {
1063 return false;
1064 }
1065
1066 return true;
1067}
1068
1069
1071{
1072 size_t initial_size = m_ruleTreeNodeDatas.size();
1073
1074 m_ruleTreeNodeDatas.erase( std::remove_if( m_ruleTreeNodeDatas.begin(), m_ruleTreeNodeDatas.end(),
1075 [aNodeId]( const RULE_TREE_NODE& node )
1076 {
1077 return node.m_nodeId == aNodeId;
1078 } ),
1079 m_ruleTreeNodeDatas.end() );
1080
1081 if( m_ruleTreeNodeDatas.size() < initial_size )
1082 return true;
1083 else
1084 return false;
1085}
1086
1087
1088void DIALOG_DRC_RULE_EDITOR::collectChildRuleNodes( int aParentId, std::vector<RULE_TREE_NODE*>& aResult )
1089{
1090 std::vector<RULE_TREE_NODE> children;
1091 getRuleTreeChildNodes( m_ruleTreeNodeDatas, aParentId, children );
1092
1093 for( const auto& child : children )
1094 {
1095 RULE_TREE_NODE* childNode = getRuleTreeNodeInfo( child.m_nodeId );
1096
1097 if( childNode->m_nodeType == RULE )
1098 aResult.push_back( childNode );
1099
1100 collectChildRuleNodes( childNode->m_nodeId, aResult );
1101 }
1102}
1103
1104
1105void DIALOG_DRC_RULE_EDITOR::collectModifiedRules( std::vector<RULE_TREE_NODE*>& aResult )
1106{
1107 for( RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
1108 {
1109 if( node.m_nodeType != RULE )
1110 continue;
1111
1112 if( node.m_nodeData && node.m_nodeData->IsNew() )
1113 {
1114 aResult.push_back( &node );
1115 continue;
1116 }
1117
1118 auto constraintData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1119
1120 if( constraintData && constraintData->WasEdited() )
1121 aResult.push_back( &node );
1122 }
1123}
1124
1125
1126bool DIALOG_DRC_RULE_EDITOR::validateAllRules( std::map<wxString, wxString>& aErrors )
1127{
1128 bool allValid = true;
1129
1130 // Track (ruleName, layerSource) pairs and their conditions to detect conflicts.
1131 // Rules with the same name and same layer scope must share the same condition to be
1132 // merged correctly. Rules with different layer scopes are saved as separate rules and
1133 // are allowed to have different conditions.
1134 std::map<std::pair<wxString, wxString>, std::set<wxString>> ruleConditions;
1135
1136 for( RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
1137 {
1138 if( node.m_nodeType != RULE )
1139 continue;
1140
1141 if( node.m_nodeData && node.m_nodeData->IsNew() )
1142 continue;
1143
1144 auto constraintData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1145
1146 if( constraintData )
1147 {
1148 // Individual constraint validation
1149 VALIDATION_RESULT result = constraintData->Validate();
1150
1151 if( !result.isValid )
1152 {
1153 wxString errorMsg;
1154
1155 for( const wxString& err : result.errors )
1156 {
1157 if( !errorMsg.IsEmpty() )
1158 errorMsg += wxS( "\n" );
1159
1160 errorMsg += err;
1161 }
1162
1163 aErrors[node.m_nodeName] = errorMsg;
1164 allValid = false;
1165 }
1166
1167 wxString ruleName = constraintData->GetRuleName();
1168 wxString condition = constraintData->GetRuleCondition();
1169 wxString layerSource = constraintData->GetLayerSource();
1170 ruleConditions[std::make_pair( ruleName, layerSource )].insert( condition );
1171 }
1172 }
1173
1174 // Check for same-name same-layer different-condition conflicts
1175 for( const auto& [key, conditions] : ruleConditions )
1176 {
1177 if( conditions.size() > 1 )
1178 {
1179 wxString errorMsg = _( "Multiple rules with the same name have different conditions. "
1180 "Rules with the same name must have identical conditions to be merged." );
1181 aErrors[key.first] = errorMsg;
1182 allValid = false;
1183 }
1184 }
1185
1186 return allValid;
1187}
1188
1189
1191{
1192 std::vector<RULE_TREE_NODE*> modifiedRules;
1193 collectModifiedRules( modifiedRules );
1194
1195 if( modifiedRules.empty() )
1196 return wxID_NO;
1197
1198 wxString message = _( "The following rules have unsaved changes:\n\n" );
1199
1200 for( RULE_TREE_NODE* rule : modifiedRules )
1201 message += wxString::Format( wxS( " \u2022 %s\n" ), rule->m_nodeName );
1202
1203 message += _( "\nDo you want to save your changes?" );
1204
1205 int result = wxMessageBox( message, _( "Save Changes?" ),
1206 wxYES_NO | wxCANCEL | wxICON_QUESTION, this );
1207
1208 if( result == wxYES )
1209 return wxID_YES;
1210 else if( result == wxNO )
1211 return wxID_NO;
1212 else
1213 return wxID_CANCEL;
1214}
1215
1216
1218{
1219 // Find the tree item ID for this node
1220 wxTreeItemIdValue cookie;
1221 wxTreeItemId root = m_ruleTreeCtrl->GetRootItem();
1222
1223 std::function<wxTreeItemId( wxTreeItemId )> findItem =
1224 [&]( wxTreeItemId parent ) -> wxTreeItemId
1225 {
1226 wxTreeItemId item = m_ruleTreeCtrl->GetFirstChild( parent, cookie );
1227
1228 while( item.IsOk() )
1229 {
1230 RULE_TREE_ITEM_DATA* data =
1231 dynamic_cast<RULE_TREE_ITEM_DATA*>( m_ruleTreeCtrl->GetItemData( item ) );
1232
1233 if( data && data->GetNodeId() == aNodeId )
1234 return item;
1235
1236 wxTreeItemId found = findItem( item );
1237
1238 if( found.IsOk() )
1239 return found;
1240
1241 item = m_ruleTreeCtrl->GetNextSibling( item );
1242 }
1243
1244 return wxTreeItemId();
1245 };
1246
1247 wxTreeItemId itemId = findItem( root );
1248
1249 if( itemId.IsOk() )
1250 m_ruleTreeCtrl->SelectItem( itemId );
1251}
1252
1253
1255 const wxString& aName, const DRC_RULE_EDITOR_ITEM_TYPE& aNodeType, const std::optional<int>& aParentId,
1256 const std::optional<DRC_RULE_EDITOR_CONSTRAINT_NAME>& aConstraintType,
1257 const std::vector<RULE_TREE_NODE>& aChildNodes, const std::optional<int>& id )
1258{
1259 unsigned int newId;
1260
1261 if( id )
1262 {
1263 newId = *id; // Use provided ID
1264 }
1265 else
1266 {
1267 newId = 1;
1268
1269 if( m_nodeId )
1270 newId = m_nodeId + 1;
1271 }
1272
1273 m_nodeId = newId;
1274
1275 RULE_EDITOR_DATA_BASE baseData;
1276 baseData.SetId( newId );
1277
1278 if( aParentId )
1279 {
1280 baseData.SetParentId( *aParentId );
1281 }
1282
1283 return { .m_nodeId = m_nodeId,
1284 .m_nodeName = aName,
1285 .m_nodeType = aNodeType,
1286 .m_nodeLevel = -1,
1287 .m_nodeTypeMap = aConstraintType,
1288 .m_childNodes = aChildNodes,
1289 .m_nodeData = std::make_shared<RULE_EDITOR_DATA_BASE>( baseData ) };
1290}
1291
1292
1293RULE_TREE_NODE DIALOG_DRC_RULE_EDITOR::buildRuleNodeFromKicadDrc( const wxString& aName, const wxString& aCode,
1294 const std::optional<int>& aParentId )
1295{
1297 RULE_TREE_NODE node =
1299
1300 auto baseData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1301 DRC_RULE_EDITOR_UTILS::ConstraintFromKicadDrc( aCode, baseData.get() );
1302 node.m_nodeData = baseData;
1303 return node;
1304}
1305
1306
1308{
1309 return !m_cancelled;
1310}
1311
1312
1313void DIALOG_DRC_RULE_EDITOR::AdvancePhase( const wxString& aMessage )
1314{
1316 SetCurrentProgress( 0.0 );
1317}
1318
1319
1324
1326{
1327 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries;
1328
1329 for( const RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
1330 {
1331 if( node.m_nodeType != RULE )
1332 continue;
1333
1334 auto data = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1335
1336 if( !data )
1337 continue;
1338
1339 if( node.m_nodeData->IsNew() )
1340 continue;
1341
1343
1344 if( node.m_nodeTypeMap )
1345 entry.panelType = static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( *node.m_nodeTypeMap );
1346 else
1347 entry.panelType = CUSTOM_RULE;
1348
1349 entry.constraintData = data;
1350 entry.ruleName = data->GetRuleName();
1351 entry.condition = data->GetRuleCondition();
1352 entry.originalRuleText = data->GetOriginalRuleText();
1353 entry.wasEdited = data->WasEdited();
1354 entry.severity = data->GetSeverity();
1355 entry.layerCondition = LSET( data->GetLayers() );
1356 entry.layerSource = data->GetLayerSource();
1357
1358 entries.push_back( entry );
1359 }
1360
1361 DRC_RULE_SAVER saver;
1362 saver.SaveFile( m_frame->GetBoard()->GetDesignRulesPath(), entries, m_currentBoard );
1363
1364 try
1365 {
1366 m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine( m_frame->GetBoard()->GetDesignRulesPath() );
1367 }
1368 catch( PARSE_ERROR& pe )
1369 {
1370 wxLogError( _( "Failed to reload DRC rules: %s" ), pe.What() );
1371 }
1372}
static TOOL_ACTION zoomFitSelection
Definition actions.h:140
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
static TOOL_ACTION selectItems
Select a list of items (specified as the event parameter)
Definition actions.h:228
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:52
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:165
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
This file is part of the common library.
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
@ MICROVIA_STACK_DEPTH
@ MINIMUM_TEXT_HEIGHT_AND_THICKNESS
@ COPPER_TO_HOLE_CLEARANCE
@ HOLE_TO_HOLE_DISTANCE
@ ROUTING_WIDTH
@ MINIMUM_DRILL_SIZE
@ MATCHED_LENGTH_DIFF_PAIR
@ COPPER_TO_EDGE_CLEARANCE
@ MICROVIA_ASPECT_RATIO
@ 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.
static wxString nodeName(const wxString &aSymbolPin)
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:83
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:103
@ PCB_NETINFO_T
class NETINFO_ITEM, a description of a net
Definition typeinfo.h:102