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