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