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
45#include "drc_re_rule_loader.h"
46#include "drc_re_rule_saver.h"
47#include <drc/drc_engine.h>
49#include <tool/tool_manager.h>
50#include <tool/actions.h>
51#include <wx/ffile.h>
52#include <functional>
53#include <memory>
54#include <set>
55
56
57const RULE_TREE_NODE* FindNodeById( const std::vector<RULE_TREE_NODE>& aNodes, int aTargetId )
58{
59 auto it = std::find_if( aNodes.begin(), aNodes.end(),
60 [aTargetId]( const RULE_TREE_NODE& node )
61 {
62 return node.m_nodeId == aTargetId;
63 } );
64
65 if( it != aNodes.end() )
66 {
67 return &( *it );
68 }
69
70 return nullptr;
71}
72
73
75 RULE_EDITOR_DIALOG_BASE( aParent, _( "Design Rule Editor" ), wxSize( 980, 800 ) ),
77 m_reporter( nullptr ),
78 m_nodeId( 0 )
79{
80 m_frame = aEditorFrame;
81 m_currentBoard = m_frame->GetBoard();
82 m_ruleEditorPanel = nullptr;
83
84 m_ruleTreeCtrl->DeleteAllItems();
85
87
89
91
93
94 if( Prj().IsReadOnly() )
95 {
96 m_infoBar->ShowMessage( _( "Project is missing or read-only. Settings will not be "
97 "editable." ),
98 wxICON_WARNING );
99 }
100
101 m_severities = 0;
102
103 m_markersProvider = std::make_shared<DRC_ITEMS_PROVIDER>( m_currentBoard, MARKER_BASE::MARKER_DRC,
105
107 new wxDataViewCtrl( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_ROW_LINES | wxDV_SINGLE );
108
110 m_markerDataView->AssociateModel( m_markersTreeModel );
112
113 m_markerDataView->Hide();
114}
115
116
120
121
123{
125
126 Layout();
127 SetMinSize( wxSize( 400, 500 ) );
128 SetSize( m_initialSize );
129
130 wxLogTrace( "debug_dlg_size", "DRC TransferDataToWindow: size=%s minSize=%s",
131 GetSize().IsFullySpecified() ? wxString::Format( "%dx%d", GetSize().x, GetSize().y )
132 : wxString( "default" ),
133 GetMinSize().IsFullySpecified()
134 ? wxString::Format( "%dx%d", GetMinSize().x, GetMinSize().y )
135 : wxString( "default" ) );
136
137 return ok;
138}
139
140
142{
144 return false;
145
147
148 return true;
149}
150
151
153{
154 std::vector<RULE_TREE_NODE> result;
155
156 int lastParentId;
157 int electricalItemId;
158 int manufacturabilityItemId;
159 int highSpeedDesignId;
160 int footprintItemId;
161
162 result.push_back( buildRuleTreeNodeData( "Design Rules", DRC_RULE_EDITOR_ITEM_TYPE::ROOT ) );
163 lastParentId = m_nodeId;
164
165 result.push_back( buildRuleTreeNodeData( "Electrical", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, lastParentId ) );
166 electricalItemId = m_nodeId;
167
168 result.push_back( buildRuleTreeNodeData( "Manufacturability", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, lastParentId ) );
169 manufacturabilityItemId = m_nodeId;
170
171 result.push_back( buildRuleTreeNodeData( "Highspeed design", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, lastParentId ) );
172 highSpeedDesignId = m_nodeId;
173
174 result.push_back( buildRuleTreeNodeData( "Footprints", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, lastParentId ) );
175 footprintItemId = m_nodeId;
176
177 std::vector<RULE_TREE_NODE> subItemNodes = buildElectricalRuleTreeNodes( electricalItemId );
178 result.insert( result.end(), subItemNodes.begin(), subItemNodes.end() );
179
180 subItemNodes = buildManufacturabilityRuleTreeNodes( manufacturabilityItemId );
181 result.insert( result.end(), subItemNodes.begin(), subItemNodes.end() );
182
183 subItemNodes = buildHighspeedDesignRuleTreeNodes( highSpeedDesignId );
184 result.insert( result.end(), subItemNodes.begin(), subItemNodes.end() );
185
186 subItemNodes = buildFootprintsRuleTreeNodes( footprintItemId );
187 result.insert( result.end(), subItemNodes.begin(), subItemNodes.end() );
188
189 // Custom rules category
190 result.push_back( buildRuleTreeNodeData( "Custom", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, lastParentId ) );
191 int customItemId = m_nodeId;
192 result.push_back(
194
195 return result;
196}
197
199{
200 wxFileName rulesFile( m_frame->GetDesignRulesPath() );
201
202 if( !rulesFile.FileExists() )
203 return;
204
205 DRC_RULE_LOADER loader;
206 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFile( rulesFile.GetFullPath() );
207
208 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
209 wxS( "[LoadExistingRules] Loaded %zu entries from %s" ),
210 entries.size(), rulesFile.GetFullPath() );
211
212 // Debug: Log all constraint nodes available for parent lookup
213 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
214 wxS( "[LoadExistingRules] Available constraint nodes in m_ruleTreeNodeDatas:" ) );
215
216 for( const RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
217 {
218 if( node.m_nodeType == CONSTRAINT )
219 {
220 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
221 wxS( "[LoadExistingRules] Node '%s': nodeId=%d, m_nodeTypeMap=%d" ),
222 wxString( node.m_nodeName ), node.m_nodeId,
223 node.m_nodeTypeMap ? static_cast<int>( *node.m_nodeTypeMap ) : -1 );
224 }
225 }
226
227 // Helper to find tree item by node ID
228 std::function<wxTreeItemId( wxTreeItemId, int )> findItem =
229 [&]( wxTreeItemId aItem, int aTargetId ) -> wxTreeItemId
230 {
231 RULE_TREE_ITEM_DATA* data =
232 dynamic_cast<RULE_TREE_ITEM_DATA*>( m_ruleTreeCtrl->GetItemData( aItem ) );
233
234 if( data && data->GetNodeId() == aTargetId )
235 return aItem;
236
237 wxTreeItemIdValue cookie;
238 wxTreeItemId child = m_ruleTreeCtrl->GetFirstChild( aItem, cookie );
239
240 while( child.IsOk() )
241 {
242 wxTreeItemId res = findItem( child, aTargetId );
243
244 if( res.IsOk() )
245 return res;
246
247 child = m_ruleTreeCtrl->GetNextChild( aItem, cookie );
248 }
249
250 return wxTreeItemId();
251 };
252
253 for( DRC_RE_LOADED_PANEL_ENTRY& entry : entries )
254 {
255 DRC_RULE_EDITOR_CONSTRAINT_NAME type = entry.panelType;
256
257 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
258 wxS( "[LoadExistingRules] Processing entry: rule='%s', panelType=%d" ),
259 entry.ruleName, static_cast<int>( type ) );
260
261 // Find parent node by constraint type
262 int parentId = -1;
263
264 for( const RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
265 {
266 if( node.m_nodeType == CONSTRAINT && node.m_nodeTypeMap && *node.m_nodeTypeMap == type )
267 {
268 parentId = node.m_nodeId;
269 break;
270 }
271 }
272
273 if( parentId == -1 )
274 {
275 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
276 wxS( "[LoadExistingRules] No parent found for panelType=%d, skipping" ),
277 static_cast<int>( type ) );
278 continue;
279 }
280
281 wxTreeItemId parentItem = findItem( m_ruleTreeCtrl->GetRootItem(), parentId );
282
283 if( !parentItem.IsOk() )
284 {
285 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
286 wxS( "[LoadExistingRules] Tree item not found for parentId=%d, skipping" ),
287 parentId );
288 continue;
289 }
290
291 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
292 wxS( "[LoadExistingRules] Found parent node: parentId=%d" ), parentId );
293
294 RULE_TREE_NODE node =
295 buildRuleTreeNodeData( entry.ruleName, RULE, parentId, type );
296
297 // Transfer loaded entry data to the constraint data
298 auto ruleData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( entry.constraintData );
299
300 if( ruleData )
301 {
302 ruleData->SetId( node.m_nodeData->GetId() );
303 ruleData->SetParentId( parentId );
304 ruleData->SetOriginalRuleText( entry.originalRuleText );
305 ruleData->SetWasEdited( entry.wasEdited );
306 ruleData->SetLayerSource( entry.layerSource );
307
308 if( !entry.layerSource.IsEmpty() )
309 ruleData->SetLayers( entry.layerCondition.Seq() );
310
311 ruleData->SetSeverity( entry.severity );
312 node.m_nodeData = ruleData;
313 }
314
315 m_ruleTreeNodeDatas.push_back( node );
316 AppendNewRuleTreeItem( node, parentItem );
317
318 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
319 wxS( "[LoadExistingRules] Appended rule '%s' to tree under parentId=%d" ),
320 entry.ruleName, parentId );
321 }
322}
323
324
326{
327 wxTreeItemId treeItemId;
328 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( aRuleTreeItemData->GetNodeId() );
329
330 if( nodeDetail->m_nodeType == CONSTRAINT )
331 {
332 treeItemId = aRuleTreeItemData->GetTreeItemId();
333 }
334 else
335 {
336 treeItemId = aRuleTreeItemData->GetParentTreeItemId();
337 }
338
339 AppendNewRuleTreeItem( buildRuleTreeNode( aRuleTreeItemData ), treeItemId );
340 SetModified();
341}
342
343
345{
346 RULE_TREE_NODE* sourceTreeNode = getRuleTreeNodeInfo( aRuleTreeItemData->GetNodeId() );
347
348 auto sourceDataPtr = dynamic_pointer_cast<RULE_EDITOR_DATA_BASE>( sourceTreeNode->m_nodeData );
349
350 if( !sourceDataPtr )
351 return;
352
353 // Strip any trailing " <number>" suffix so the number increments
354 wxString baseName = sourceDataPtr->GetRuleName();
355 int lastSpace = baseName.Find( ' ', true );
356
357 if( lastSpace != wxNOT_FOUND )
358 {
359 wxString suffix = baseName.Mid( lastSpace + 1 );
360 long num;
361
362 if( suffix.ToLong( &num ) )
363 baseName = baseName.Left( lastSpace );
364 }
365
366 RULE_TREE_NODE targetTreeNode = buildRuleTreeNode( aRuleTreeItemData, baseName );
367 targetTreeNode.m_nodeData->CopyFrom( *sourceDataPtr );
368
369 wxTreeItemId treeItemId = aRuleTreeItemData->GetParentTreeItemId();
370 AppendNewRuleTreeItem( targetTreeNode, treeItemId );
371 SetModified();
372}
373
374
376{
377 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( aCurrentRuleTreeItemData->GetNodeId() );
378
379 if( nodeDetail->m_nodeType == ROOT || nodeDetail->m_nodeType == CATEGORY || nodeDetail->m_nodeType == CONSTRAINT )
380 {
381 std::vector<RULE_TREE_NODE*> ruleNodes;
382 collectChildRuleNodes( nodeDetail->m_nodeId, ruleNodes );
383
384 std::vector<DRC_RULE_ROW> rows;
385 rows.reserve( ruleNodes.size() );
386
387 for( RULE_TREE_NODE* ruleNode : ruleNodes )
388 {
389 RULE_TREE_NODE* parentNode = getRuleTreeNodeInfo( ruleNode->m_nodeData->GetParentId() );
390 wxString type = parentNode ? parentNode->m_nodeName : wxString{};
391 rows.push_back( { type, ruleNode->m_nodeData->GetRuleName(), ruleNode->m_nodeData->GetComment() } );
392 }
393
396 m_ruleEditorPanel = nullptr;
397 }
398 else if( nodeDetail->m_nodeType == RULE )
399 {
400 RULE_TREE_ITEM_DATA* parentItemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
401 m_ruleTreeCtrl->GetItemData( aCurrentRuleTreeItemData->GetParentTreeItemId() ) );
402 RULE_TREE_NODE* paretNodeDetail = getRuleTreeNodeInfo( parentItemData->GetNodeId() );
403 wxString constraintName = paretNodeDetail->m_nodeName;
404
406 m_scrolledContentWin, m_frame->GetBoard(),
407 static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( nodeDetail->m_nodeTypeMap.value_or( -1 ) ),
408 &constraintName, dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( nodeDetail->m_nodeData ) );
409
411
412 m_ruleEditorPanel->TransferDataToWindow();
413
414 m_ruleEditorPanel->SetSaveCallback(
415 [this]( int aNodeId )
416 {
417 this->saveRule( aNodeId );
418 } );
419
420 m_ruleEditorPanel->SetRemoveCallback(
421 [this]( int aNodeId )
422 {
423 this->RemoveRule( aNodeId );
424 } );
425
426 m_ruleEditorPanel->SetCloseCallback(
427 [this]( int aNodeId )
428 {
429 this->closeRuleEntryView( aNodeId );
430 } );
431
432 m_ruleEditorPanel->SetRuleNameValidationCallback(
433 [this]( int aNodeId, wxString aRuleName )
434 {
435 return this->validateRuleName( aNodeId, aRuleName );
436 } );
437
438 m_ruleEditorPanel->SetShowMatchesCallBack(
439 [this]( int aNodeId ) -> int
440 {
441 return this->highlightMatchingItems( aNodeId );
442 } );
443
444 m_groupHeaderPanel = nullptr;
445 }
446}
447
448
449void DIALOG_DRC_RULE_EDITOR::OnSave( wxCommandEvent& aEvent )
450{
452 m_ruleEditorPanel->Save( aEvent );
453}
454
455
456void DIALOG_DRC_RULE_EDITOR::OnCancel( wxCommandEvent& aEvent )
457{
458 // If currently editing a panel, cancel that first
460 {
461 auto data = m_ruleEditorPanel->GetConstraintData();
462 bool isNew = data && data->IsNew();
463
464 m_ruleEditorPanel->Cancel( aEvent );
465
466 if( isNew )
467 {
468 // After canceling a new rule, check if there are any remaining modified rules
469 std::vector<RULE_TREE_NODE*> modifiedRules;
470 collectModifiedRules( modifiedRules );
471
472 if( modifiedRules.empty() )
474
475 return;
476 }
477 }
478
479 // If there are unsaved changes, prompt the user
480 if( IsModified() )
481 {
483
484 if( result == wxID_CANCEL )
485 return;
486
487 if( result == wxID_YES )
488 {
489 // Validate all rules before saving
490 std::map<wxString, wxString> errors;
491
492 if( !validateAllRules( errors ) )
493 {
494 // Find the first rule with an error and select it
496 {
497 if( errors.find( node.m_nodeName ) != errors.end() )
498 {
499 selectRuleNode( node.m_nodeId );
500
501 wxString msg = wxString::Format(
502 _( "Cannot save due to validation errors in rule '%s':\n\n%s" ),
503 node.m_nodeName, errors[node.m_nodeName] );
504 DisplayErrorMessage( this, msg );
505 return;
506 }
507 }
508
509 return;
510 }
511
514 }
515 }
516
517 aEvent.Skip();
518}
519
520
522{
523 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( aRuleTreeItemData->GetNodeId() );
524
526 {
527 m_ruleEditorPanel->TransferDataFromWindow();
528
529 nodeDetail->m_nodeName = nodeDetail->m_nodeData->GetRuleName();
530 nodeDetail->m_nodeData->SetIsNew( false );
531
532 // Mark as edited so the rule gets regenerated instead of using original text
533 auto constraintData =
534 std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( nodeDetail->m_nodeData );
535
536 if( constraintData )
537 constraintData->SetWasEdited( true );
538
539 UpdateRuleTreeItemText( aRuleTreeItemData->GetTreeItemId(), nodeDetail->m_nodeName );
540 }
541}
542
543
562
563
565{
566 RULE_TREE_ITEM_DATA* itemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
567 m_ruleTreeCtrl->GetItemData( GetCurrentlySelectedRuleTreeItemData()->GetTreeItemId() ) );
568 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( itemData->GetNodeId() );
569
570 if( !nodeDetail->m_nodeData->IsNew() )
571 {
572 if( OKOrCancelDialog( this, _( "Confirmation" ), "", _( "Are you sure you want to delete?" ), _( "Delete" ) )
573 != wxID_OK )
574 {
575 return;
576 }
577 }
578
579 if( itemData )
580 {
581 int nodeId = itemData->GetNodeId();
582
583 DeleteRuleTreeItem( GetCurrentlySelectedRuleTreeItemData()->GetTreeItemId(), nodeId );
584 deleteTreeNodeData( nodeId );
585 SetModified();
586 }
587
588 SetControlsEnabled( true );
589}
590
591
592std::vector<RULE_TREE_NODE> DIALOG_DRC_RULE_EDITOR::buildElectricalRuleTreeNodes( int& aParentId )
593{
594 std::vector<RULE_TREE_NODE> result;
595 int lastParentId;
596
597 result.push_back( buildRuleTreeNodeData( "Clearance", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
598 lastParentId = m_nodeId;
599
600 result.push_back( buildRuleTreeNodeData( "Minimum clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
602 result.push_back( buildRuleTreeNodeData( "Copper to edge clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
603 lastParentId, COPPER_TO_EDGE_CLEARANCE ) );
604 result.push_back( buildRuleTreeNodeData( "Courtyard Clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
606 result.push_back( buildRuleTreeNodeData( "Physical Clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
608 result.push_back( buildRuleTreeNodeData( "Creepage distance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
610
611 result.push_back( buildRuleTreeNodeData( "Connection Width", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
612 lastParentId = m_nodeId;
613
614 result.push_back( buildRuleTreeNodeData( "Minimum connection width", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
615 lastParentId, MINIMUM_CONNECTION_WIDTH ) );
616
617 result.push_back( buildRuleTreeNodeData( "Hole Clearance", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
618 lastParentId = m_nodeId;
619
620 result.push_back( buildRuleTreeNodeData( "Copper to hole clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
621 lastParentId, COPPER_TO_HOLE_CLEARANCE ) );
622 result.push_back( buildRuleTreeNodeData( "Hole to hole clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
623 lastParentId, HOLE_TO_HOLE_CLEARANCE ) );
624
625 result.push_back( buildRuleTreeNodeData( "Spoke Count", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
626 lastParentId = m_nodeId;
627
628 result.push_back( buildRuleTreeNodeData( "Minimum thermal relief spoke count",
631
632 return result;
633}
634
635
636std::vector<RULE_TREE_NODE> DIALOG_DRC_RULE_EDITOR::buildManufacturabilityRuleTreeNodes( int& aParentId )
637{
638 std::vector<RULE_TREE_NODE> result;
639 int lastParentId;
640
641 result.push_back( buildRuleTreeNodeData( "Annular Width", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
642 lastParentId = m_nodeId;
643
644 result.push_back( buildRuleTreeNodeData( "Minimum annular width", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
645 lastParentId, MINIMUM_ANNULAR_WIDTH ) );
646 result.push_back( buildRuleTreeNodeData( "Hole", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
647 lastParentId = m_nodeId;
648 result.push_back( buildRuleTreeNodeData( "Minimum through hole", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
649 lastParentId, MINIMUM_THROUGH_HOLE ) );
650 result.push_back( buildRuleTreeNodeData( "Hole to hole distance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
651 lastParentId, HOLE_TO_HOLE_DISTANCE ) );
652 result.push_back( buildRuleTreeNodeData( "Minimum uvia hole", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT, lastParentId,
654 result.push_back( buildRuleTreeNodeData( "Minimum uvia diameter", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
655 lastParentId, MINIMUM_UVIA_DIAMETER ) );
656 result.push_back(
658
659 result.push_back( buildRuleTreeNodeData( "Text Geometry", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
660 lastParentId = m_nodeId;
661
662 result.push_back( buildRuleTreeNodeData( "Minimum text height and thickness", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
663 lastParentId, MINIMUM_TEXT_HEIGHT_AND_THICKNESS ) );
664
665 result.push_back( buildRuleTreeNodeData( "Silkscreen Clearance", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
666 lastParentId = m_nodeId;
667
668 result.push_back( buildRuleTreeNodeData( "Silk to silk clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
669 lastParentId, SILK_TO_SILK_CLEARANCE ) );
670 result.push_back( buildRuleTreeNodeData( "Silk to soldermask clearance", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
671 lastParentId, SILK_TO_SOLDERMASK_CLEARANCE ) );
672
673 result.push_back( buildRuleTreeNodeData( "Soldermask", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
674 lastParentId = m_nodeId;
675
676 result.push_back( buildRuleTreeNodeData( "Minimum soldermask silver", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
677 lastParentId, MINIMUM_SOLDERMASK_SILVER ) );
678 result.push_back( buildRuleTreeNodeData( "Soldermask expansion", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
679 lastParentId, SOLDERMASK_EXPANSION ) );
680
681 result.push_back( buildRuleTreeNodeData( "Solderpaste", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
682 lastParentId = m_nodeId;
683
684 result.push_back( buildRuleTreeNodeData( "Solderpaste expansion", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
685 lastParentId, SOLDERPASTE_EXPANSION ) );
686
687 result.push_back( buildRuleTreeNodeData( "Deviation", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
688 lastParentId = m_nodeId;
689
690 result.push_back( buildRuleTreeNodeData( "Maximum allowed deviation", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
691 lastParentId, MAXIMUM_ALLOWED_DEVIATION ) );
692
693 result.push_back( buildRuleTreeNodeData( "Annular Ring", DRC_RULE_EDITOR_ITEM_TYPE::CATEGORY, aParentId ) );
694 lastParentId = m_nodeId;
695
696 result.push_back( buildRuleTreeNodeData( "Minimum annular ring", DRC_RULE_EDITOR_ITEM_TYPE::CONSTRAINT,
697 lastParentId, MINIMUM_ANGULAR_RING ) );
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 } }
864 };
865
866 RULE_TREE_ITEM_DATA* treeItemData;
867 RULE_TREE_NODE* nodeDetail = getRuleTreeNodeInfo( aRuleTreeItemData->GetNodeId() );
868
869 if( nodeDetail->m_nodeType == CONSTRAINT )
870 {
871 treeItemData = aRuleTreeItemData;
872 }
873 else
874 {
875 treeItemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
876 m_ruleTreeCtrl->GetItemData( aRuleTreeItemData->GetParentTreeItemId() ) );
877 nodeDetail = getRuleTreeNodeInfo( treeItemData->GetNodeId() );
878 }
879
880 m_nodeId++;
881
882 wxString base = aBaseName.IsEmpty() ? nodeDetail->m_nodeName : aBaseName;
883 wxString nodeName = base + " 1";
884
885 int loop = 2;
886 bool check = false;
887
888 do
889 {
890 check = false;
891
892 if( nodeExists( m_ruleTreeNodeDatas, nodeName ) )
893 {
894 nodeName = base + wxString::Format( " %d", loop );
895 loop++;
896 check = true;
897 }
898 } while( check );
899
901 nodeName, RULE, nodeDetail->m_nodeId,
902 static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( nodeDetail->m_nodeTypeMap.value_or( 0 ) ), {}, m_nodeId );
903
904 auto nodeType = static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( newRuleNode.m_nodeTypeMap.value_or( -1 ) );
905
906 DRC_RE_BASE_CONSTRAINT_DATA clearanceData( m_nodeId, nodeDetail->m_nodeData->GetId(), newRuleNode.m_nodeName );
907
908 if( s_constraintFactories.find( nodeType ) != s_constraintFactories.end() )
909 {
910 newRuleNode.m_nodeData = s_constraintFactories.at( nodeType )( clearanceData );
911 }
912 else if( DRC_RULE_EDITOR_UTILS::IsNumericInputType( nodeType ) )
913 {
914 newRuleNode.m_nodeData = DRC_RULE_EDITOR_UTILS::CreateNumericConstraintData( nodeType, clearanceData );
915 }
916 else if( DRC_RULE_EDITOR_UTILS::IsBoolInputType( nodeType ) )
917 {
918 newRuleNode.m_nodeData = std::make_shared<DRC_RE_BOOL_INPUT_CONSTRAINT_DATA>( clearanceData );
919 }
920 else
921 {
922 wxLogWarning( "No factory found for constraint type: %d", nodeType );
923 newRuleNode.m_nodeData = std::make_shared<DRC_RE_BASE_CONSTRAINT_DATA>( clearanceData );
924 }
925
926 std::static_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( newRuleNode.m_nodeData )
927 ->SetConstraintCode( DRC_RULE_EDITOR_UTILS::ConstraintToKicadDrc( nodeType ) );
928 newRuleNode.m_nodeData->SetIsNew( true );
929
930 m_ruleTreeNodeDatas.push_back( newRuleNode );
931
932 return newRuleNode;
933}
934
935
937{
938 auto it = std::find_if( m_ruleTreeNodeDatas.begin(), m_ruleTreeNodeDatas.end(),
939 [aNodeId]( const RULE_TREE_NODE& node )
940 {
941 return node.m_nodeId == aNodeId;
942 } );
943
944 if( it != m_ruleTreeNodeDatas.end() )
945 {
946 return &( *it ); // Return pointer to the found node
947 }
948 else
949 return nullptr;
950}
951
952
954{
955 if( !m_ruleEditorPanel->GetIsValidationSucceeded() )
956 {
957 wxString validationMessage = m_ruleEditorPanel->GetValidationMessage();
958
959 DisplayErrorMessage( this, validationMessage );
960 }
961 else
962 {
963 RULE_TREE_ITEM_DATA* itemData = dynamic_cast<RULE_TREE_ITEM_DATA*>(
964 m_ruleTreeCtrl->GetItemData( GetCurrentlySelectedRuleTreeItemData()->GetTreeItemId() ) );
965
966 if( itemData )
967 {
968 UpdateRuleTypeTreeItemData( itemData );
969 }
970
973
974 SetControlsEnabled( true );
975 }
976}
977
978
980{
981 SetControlsEnabled( true );
982}
983
984
986{
987 (void) aNodeId;
988
989 if( !m_ruleEditorPanel )
990 return -1;
991
992 // Ensure we use the latest text from the condition editor
993 m_ruleEditorPanel->TransferDataFromWindow();
994 wxString condition = m_ruleEditorPanel->GetConstraintData()->GetRuleCondition();
995
996 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
997 wxS( "[ShowMatches] nodeId=%d, condition='%s'" ), aNodeId, condition );
998
999 // Empty condition matches nothing
1000 if( condition.IsEmpty() )
1001 {
1002 m_frame->FocusOnItems( {} );
1003 Raise();
1004 return 0;
1005 }
1006
1007 // Pre-compile condition to detect syntax errors
1008 DRC_RULE_CONDITION testCondition( condition );
1009
1010 if( !testCondition.Compile( nullptr ) )
1011 {
1012 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ), wxS( "[ShowMatches] compile failed" ) );
1013 return -1;
1014 }
1015
1016 m_drcTool = m_frame->GetToolManager()->GetTool<DRC_TOOL>();
1017
1018 std::vector<BOARD_ITEM*> allMatches =
1019 m_drcTool->GetDRCEngine()->GetItemsMatchingCondition( condition, ASSERTION_CONSTRAINT, m_reporter );
1020
1021 // Filter out items without visible geometry
1022 std::vector<BOARD_ITEM*> matches;
1023
1024 for( BOARD_ITEM* item : allMatches )
1025 {
1026 switch( item->Type() )
1027 {
1028 case PCB_NETINFO_T:
1029 case PCB_GENERATOR_T:
1030 case PCB_GROUP_T:
1031 continue;
1032
1033 default:
1034 matches.push_back( item );
1035 break;
1036 }
1037 }
1038
1039 int matchCount = static_cast<int>( matches.size() );
1040
1041 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ), wxS( "[ShowMatches] matched_count=%d (filtered from %zu)" ),
1042 matchCount, allMatches.size() );
1043
1044 // Clear any existing selection and select matched items
1045 m_frame->GetToolManager()->RunAction( ACTIONS::selectionClear );
1046
1047 if( matches.size() > 0 )
1048 {
1049 std::vector<EDA_ITEM*> selectItems;
1050
1051 for( BOARD_ITEM* item : matches )
1052 selectItems.push_back( item );
1053
1054 m_frame->GetToolManager()->RunAction( ACTIONS::selectItems, &selectItems );
1055 m_frame->GetToolManager()->RunAction( ACTIONS::zoomFitSelection );
1056 }
1057
1058 // Also brighten items to provide additional visual feedback
1059 m_frame->FocusOnItems( matches );
1060 Raise();
1061
1062 return matchCount;
1063}
1064
1065
1066bool DIALOG_DRC_RULE_EDITOR::validateRuleName( int aNodeId, wxString aRuleName )
1067{
1068 auto it = std::find_if( m_ruleTreeNodeDatas.begin(), m_ruleTreeNodeDatas.end(),
1069 [aNodeId, aRuleName]( const RULE_TREE_NODE& node )
1070 {
1071 return node.m_nodeName == aRuleName && node.m_nodeId != aNodeId
1072 && node.m_nodeType == RULE;
1073 } );
1074
1075 if( it != m_ruleTreeNodeDatas.end() )
1076 {
1077 return false;
1078 }
1079
1080 return true;
1081}
1082
1083
1085{
1086 size_t initial_size = m_ruleTreeNodeDatas.size();
1087
1088 m_ruleTreeNodeDatas.erase( std::remove_if( m_ruleTreeNodeDatas.begin(), m_ruleTreeNodeDatas.end(),
1089 [aNodeId]( const RULE_TREE_NODE& node )
1090 {
1091 return node.m_nodeId == aNodeId;
1092 } ),
1093 m_ruleTreeNodeDatas.end() );
1094
1095 if( m_ruleTreeNodeDatas.size() < initial_size )
1096 return true;
1097 else
1098 return false;
1099}
1100
1101
1102void DIALOG_DRC_RULE_EDITOR::collectChildRuleNodes( int aParentId, std::vector<RULE_TREE_NODE*>& aResult )
1103{
1104 std::vector<RULE_TREE_NODE> children;
1105 getRuleTreeChildNodes( m_ruleTreeNodeDatas, aParentId, children );
1106
1107 for( const auto& child : children )
1108 {
1109 RULE_TREE_NODE* childNode = getRuleTreeNodeInfo( child.m_nodeId );
1110
1111 if( childNode->m_nodeType == RULE )
1112 aResult.push_back( childNode );
1113
1114 collectChildRuleNodes( childNode->m_nodeId, aResult );
1115 }
1116}
1117
1118
1119void DIALOG_DRC_RULE_EDITOR::collectModifiedRules( std::vector<RULE_TREE_NODE*>& aResult )
1120{
1121 for( RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
1122 {
1123 if( node.m_nodeType != RULE )
1124 continue;
1125
1126 if( node.m_nodeData && node.m_nodeData->IsNew() )
1127 {
1128 aResult.push_back( &node );
1129 continue;
1130 }
1131
1132 auto constraintData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1133
1134 if( constraintData && constraintData->WasEdited() )
1135 aResult.push_back( &node );
1136 }
1137}
1138
1139
1140bool DIALOG_DRC_RULE_EDITOR::validateAllRules( std::map<wxString, wxString>& aErrors )
1141{
1142 bool allValid = true;
1143
1144 // Track rule names and their conditions to detect same-name different-condition conflicts
1145 std::map<wxString, std::set<wxString>> ruleConditions;
1146
1147 for( RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
1148 {
1149 if( node.m_nodeType != RULE )
1150 continue;
1151
1152 auto constraintData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1153
1154 if( constraintData )
1155 {
1156 // Individual constraint validation
1157 VALIDATION_RESULT result = constraintData->Validate();
1158
1159 if( !result.isValid )
1160 {
1161 wxString errorMsg;
1162
1163 for( const wxString& err : result.errors )
1164 {
1165 if( !errorMsg.IsEmpty() )
1166 errorMsg += wxS( "\n" );
1167
1168 errorMsg += err;
1169 }
1170
1171 aErrors[node.m_nodeName] = errorMsg;
1172 allValid = false;
1173 }
1174
1175 // Track conditions for same-name different-condition validation
1176 wxString ruleName = constraintData->GetRuleName();
1177 wxString condition = constraintData->GetRuleCondition();
1178 ruleConditions[ruleName].insert( condition );
1179 }
1180 }
1181
1182 // Check for same-name different-condition conflicts
1183 for( const auto& [ruleName, conditions] : ruleConditions )
1184 {
1185 if( conditions.size() > 1 )
1186 {
1187 wxString errorMsg = _( "Multiple rules with the same name have different conditions. "
1188 "Rules with the same name must have identical conditions to be merged." );
1189 aErrors[ruleName] = errorMsg;
1190 allValid = false;
1191 }
1192 }
1193
1194 return allValid;
1195}
1196
1197
1199{
1200 std::vector<RULE_TREE_NODE*> modifiedRules;
1201 collectModifiedRules( modifiedRules );
1202
1203 if( modifiedRules.empty() )
1204 return wxID_NO;
1205
1206 wxString message = _( "The following rules have unsaved changes:\n\n" );
1207
1208 for( RULE_TREE_NODE* rule : modifiedRules )
1209 message += wxString::Format( wxS( " \u2022 %s\n" ), rule->m_nodeName );
1210
1211 message += _( "\nDo you want to save your changes?" );
1212
1213 int result = wxMessageBox( message, _( "Save Changes?" ),
1214 wxYES_NO | wxCANCEL | wxICON_QUESTION, this );
1215
1216 if( result == wxYES )
1217 return wxID_YES;
1218 else if( result == wxNO )
1219 return wxID_NO;
1220 else
1221 return wxID_CANCEL;
1222}
1223
1224
1226{
1227 // Find the tree item ID for this node
1228 wxTreeItemIdValue cookie;
1229 wxTreeItemId root = m_ruleTreeCtrl->GetRootItem();
1230
1231 std::function<wxTreeItemId( wxTreeItemId )> findItem = [&]( wxTreeItemId parent ) -> wxTreeItemId
1232 {
1233 wxTreeItemId item = m_ruleTreeCtrl->GetFirstChild( parent, cookie );
1234
1235 while( item.IsOk() )
1236 {
1237 RULE_TREE_ITEM_DATA* data =
1238 dynamic_cast<RULE_TREE_ITEM_DATA*>( m_ruleTreeCtrl->GetItemData( item ) );
1239
1240 if( data && data->GetNodeId() == aNodeId )
1241 return item;
1242
1243 wxTreeItemId found = findItem( item );
1244
1245 if( found.IsOk() )
1246 return found;
1247
1248 item = m_ruleTreeCtrl->GetNextSibling( item );
1249 }
1250
1251 return wxTreeItemId();
1252 };
1253
1254 wxTreeItemId itemId = findItem( root );
1255
1256 if( itemId.IsOk() )
1257 m_ruleTreeCtrl->SelectItem( itemId );
1258}
1259
1260
1262 const wxString& aName, const DRC_RULE_EDITOR_ITEM_TYPE& aNodeType, const std::optional<int>& aParentId,
1263 const std::optional<DRC_RULE_EDITOR_CONSTRAINT_NAME>& aConstraintType,
1264 const std::vector<RULE_TREE_NODE>& aChildNodes, const std::optional<int>& id )
1265{
1266 unsigned int newId;
1267
1268 if( id )
1269 {
1270 newId = *id; // Use provided ID
1271 }
1272 else
1273 {
1274 newId = 1;
1275
1276 if( m_nodeId )
1277 newId = m_nodeId + 1;
1278 }
1279
1280 m_nodeId = newId;
1281
1282 RULE_EDITOR_DATA_BASE baseData;
1283 baseData.SetId( newId );
1284
1285 if( aParentId )
1286 {
1287 baseData.SetParentId( *aParentId );
1288 }
1289
1290 return { .m_nodeId = m_nodeId,
1291 .m_nodeName = aName,
1292 .m_nodeType = aNodeType,
1293 .m_nodeLevel = -1,
1294 .m_nodeTypeMap = aConstraintType,
1295 .m_childNodes = aChildNodes,
1296 .m_nodeData = std::make_shared<RULE_EDITOR_DATA_BASE>( baseData ) };
1297}
1298
1299
1300RULE_TREE_NODE DIALOG_DRC_RULE_EDITOR::buildRuleNodeFromKicadDrc( const wxString& aName, const wxString& aCode,
1301 const std::optional<int>& aParentId )
1302{
1304 RULE_TREE_NODE node =
1306
1307 auto baseData = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1308 DRC_RULE_EDITOR_UTILS::ConstraintFromKicadDrc( aCode, baseData.get() );
1309 node.m_nodeData = baseData;
1310 return node;
1311}
1312
1313
1315{
1316 return !m_cancelled;
1317}
1318
1319
1320void DIALOG_DRC_RULE_EDITOR::AdvancePhase( const wxString& aMessage )
1321{
1323 SetCurrentProgress( 0.0 );
1324}
1325
1326
1331
1333{
1334 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries;
1335
1336 for( const RULE_TREE_NODE& node : m_ruleTreeNodeDatas )
1337 {
1338 if( node.m_nodeType != RULE )
1339 continue;
1340
1341 auto data = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( node.m_nodeData );
1342
1343 if( !data )
1344 continue;
1345
1347
1348 if( node.m_nodeTypeMap )
1349 entry.panelType = static_cast<DRC_RULE_EDITOR_CONSTRAINT_NAME>( *node.m_nodeTypeMap );
1350 else
1351 entry.panelType = CUSTOM_RULE;
1352
1353 entry.constraintData = data;
1354 entry.ruleName = data->GetRuleName();
1355 entry.condition = data->GetRuleCondition();
1356 entry.originalRuleText = data->GetOriginalRuleText();
1357 entry.wasEdited = data->WasEdited();
1358 entry.severity = data->GetSeverity();
1359 entry.layerCondition = LSET( data->GetLayers() );
1360 entry.layerSource = data->GetLayerSource();
1361
1362 entries.push_back( entry );
1363 }
1364
1365 DRC_RULE_SAVER saver;
1366 saver.SaveFile( m_frame->GetDesignRulesPath(), entries, m_currentBoard );
1367
1368 try
1369 {
1370 m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine(
1371 m_frame->GetDesignRulesPath() );
1372 }
1373 catch( PARSE_ERROR& pe )
1374 {
1375 wxLogError( _( "Failed to reload DRC rules: %s" ), pe.What() );
1376 }
1377}
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.
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.
bool validateRuleName(int aNodeId, wxString aRuleName)
Validates if the rule name is unique for the given node ID.
wxSize m_initialSize
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)
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
@ MINIMUM_SOLDERMASK_SILVER
@ ALLOWED_ORIENTATION
@ SILK_TO_SILK_CLEARANCE
@ ROUTING_DIFF_PAIR
@ HOLE_TO_HOLE_CLEARANCE
@ SOLDERPASTE_EXPANSION
@ SILK_TO_SOLDERMASK_CLEARANCE
@ MINIMUM_ANGULAR_RING
@ MINIMUM_UVIA_HOLE
@ COURTYARD_CLEARANCE
@ MINIMUM_CONNECTION_WIDTH
@ SOLDERMASK_EXPANSION
@ MAXIMUM_VIA_COUNT
@ MINIMUM_ANNULAR_WIDTH
@ PHYSICAL_CLEARANCE
@ MINIMUM_THROUGH_HOLE
@ CREEPAGE_DISTANCE
@ ABSOLUTE_LENGTH
@ MINIMUM_CLEARANCE
@ MINIMUM_THERMAL_RELIEF_SPOKE_COUNT
@ PERMITTED_LAYERS
@ MINIMUM_TEXT_HEIGHT_AND_THICKNESS
@ COPPER_TO_HOLE_CLEARANCE
@ MAXIMUM_ALLOWED_DEVIATION
@ MINIMUM_UVIA_DIAMETER
@ HOLE_TO_HOLE_DISTANCE
@ MATCHED_LENGTH_DIFF_PAIR
@ COPPER_TO_EDGE_CLEARANCE
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.
VECTOR3I res
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