KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_setup_rules.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <bitmaps.h>
21#include <common.h>
22#include <confirm.h>
23#include <kiplatform/io.h>
26#include <pcb_edit_frame.h>
27#include <pcbexpr_evaluator.h>
28#include <board.h>
30#include <drc/drc_engine.h>
33#include <project.h>
34#include <string_utils.h>
35#include <tool/tool_manager.h>
36#include <panel_setup_rules.h>
39#include <scintilla_tricks.h>
40#include <drc/drc_rule.h>
42#include <drc/drc_rule_parser.h>
43#include <tools/drc_tool.h>
44#include <pgm_base.h>
46#include <regex>
47#include <unordered_map>
48#include <properties/property.h>
50
51PANEL_SETUP_RULES::PANEL_SETUP_RULES( wxWindow* aParentWindow, PCB_EDIT_FRAME* aFrame ) :
52 PANEL_SETUP_RULES_BASE( aParentWindow ),
53 m_frame( aFrame ),
54 m_scintillaTricks( nullptr ),
55 m_helpWindow( nullptr )
56{
57 m_scintillaTricks = new SCINTILLA_TRICKS( m_textEditor, wxT( "()" ), false,
58 // onAcceptFn
59 [this]( wxKeyEvent& aEvent )
60 {
61 wxPostEvent( PAGED_DIALOG::GetDialog( this ),
62 wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
63 },
64 // onCharFn
65 [this]( wxStyledTextEvent& aEvent )
66 {
67 onScintillaCharAdded( aEvent );
68 } );
69
70 m_textEditor->AutoCompSetSeparator( '|' );
71
72 m_netClassRegex.Compile( "^NetClass\\s*[!=]=\\s*$", wxRE_ADVANCED );
73 m_netNameRegex.Compile( "^NetName\\s*[!=]=\\s*$", wxRE_ADVANCED );
74 m_typeRegex.Compile( "^Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
75 m_viaTypeRegex.Compile( "^Via_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
76 m_padTypeRegex.Compile( "^Pad_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
77 m_pinTypeRegex.Compile( "^Pin_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
78 m_fabPropRegex.Compile( "^Fabrication_Property\\s*[!=]=\\s*$", wxRE_ADVANCED );
79 m_shapeRegex.Compile( "^Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
80 m_padShapeRegex.Compile( "^Pad_Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
81 m_padConnectionsRegex.Compile( "^Pad_Connections\\s*[!=]=\\s*$", wxRE_ADVANCED );
82 m_zoneConnStyleRegex.Compile( "^Zone_Connection_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
83 m_lineStyleRegex.Compile( "^Line_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
84 m_hJustRegex.Compile( "^Horizontal_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
85 m_vJustRegex.Compile( "^Vertical_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
86
88
89 m_textEditor->SetZoom( Pgm().GetCommonSettings()->m_Appearance.text_editor_zoom );
90
91 m_textEditor->UsePopUp( 0 );
92 m_textEditor->Bind( wxEVT_CHAR_HOOK, &PANEL_SETUP_RULES::onCharHook, this );
93}
94
95
97{
98 m_textEditor->Unbind( wxEVT_CHAR_HOOK, &PANEL_SETUP_RULES::onCharHook, this );
100
101 delete m_scintillaTricks;
102 m_scintillaTricks = nullptr;
103
104 if( m_helpWindow )
105 m_helpWindow->Destroy();
106};
107
108
109void PANEL_SETUP_RULES::onCharHook( wxKeyEvent& aEvent )
110{
111 if( aEvent.GetKeyCode() == WXK_ESCAPE && !m_textEditor->AutoCompActive() )
112 {
113 if( m_originalText != m_textEditor->GetText() )
114 {
115 if( IsOK( wxGetTopLevelParent( this ), _( "Cancel Changes?" ) ) )
116 {
117 m_textEditor->SetText( m_originalText );
118 m_textEditor->SelectAll();
119 }
120
121 return;
122 }
123 }
124
125 aEvent.Skip();
126}
127
128
129void PANEL_SETUP_RULES::OnContextMenu(wxMouseEvent &event)
130{
131 wxMenu menu;
132
133 menu.Append( wxID_UNDO, _( "Undo" ) );
134 menu.Append( wxID_REDO, _( "Redo" ) );
135
136 menu.AppendSeparator();
137
138 menu.Append( 1, _( "Cut" ) ); // Don't use wxID_CUT, wxID_COPY, etc. On Mac (at least),
139 menu.Append( 2, _( "Copy" ) ); // wxWidgets never delivers them to us.
140 menu.Append( 3, _( "Paste" ) );
141 menu.Append( 4, _( "Delete" ) );
142
143 menu.AppendSeparator();
144
145 menu.Append( 5, _( "Select All" ) );
146
147 menu.AppendSeparator();
148
149 menu.Append( wxID_ZOOM_IN, _( "Zoom In" ) );
150 menu.Append( wxID_ZOOM_OUT, _( "Zoom Out" ) );
151
152 menu.AppendSeparator();
153
154 menu.Append( 6, _( "Show Matching Items" ) );
155
156 switch( GetPopupMenuSelectionFromUser( menu ) )
157 {
158 case wxID_UNDO:
159 m_textEditor->Undo();
160 break;
161 case wxID_REDO:
162 m_textEditor->Redo();
163 break;
164
165 case 1:
166 m_textEditor->Cut();
167 break;
168 case 2:
169 m_textEditor->Copy();
170 break;
171 case 3:
172 m_textEditor->Paste();
173 break;
174 case 4:
175 {
176 long from, to;
177 m_textEditor->GetSelection( &from, &to );
178
179 if( to > from )
180 m_textEditor->DeleteRange( from, to );
181
182 break;
183 }
184
185 case 5:
186 m_textEditor->SelectAll();
187 break;
188
189 case wxID_ZOOM_IN:
190 m_textEditor->ZoomIn();
191 break;
192 case wxID_ZOOM_OUT:
193 m_textEditor->ZoomOut();
194 break;
195
196 case 6: OnShowMatching(); break;
197 }
198}
199
200
202{
203 m_errorsReport->Clear();
204
205 if( m_frame->Prj().IsNullProject() )
206 {
207 m_errorsReport->Report( _( "Cannot show matching items without an open project." ), RPT_SEVERITY_ERROR );
208 m_errorsReport->Flush();
209 return;
210 }
211
212 const wxString text = m_textEditor->GetText();
213 const int cursorPos = m_textEditor->GetCurrentPos();
214 const int cursorLine = m_textEditor->LineFromPosition( cursorPos ) + 1;
215 const int cursorCol = cursorPos - m_textEditor->PositionFromLine( cursorLine - 1 ) + 1;
216
217 std::vector<std::shared_ptr<DRC_RULE>> rules;
218
219 try
220 {
221 std::function<bool( wxString* )> resolver =
222 [&]( wxString* token ) -> bool
223 {
224 if( IsComponentClassSelector( *token ) )
225 return false;
226
227 return m_frame->GetBoard()->ResolveTextVar( token, 0 );
228 };
229
230 wxString rulesText = m_frame->GetBoard()->ConvertCrossReferencesToKIIDs( text );
231 rulesText = ExpandTextVars( rulesText, &resolver );
232
233 DRC_RULES_PARSER parser( rulesText, _( "DRC rules" ) );
234 parser.Parse( rules, m_errorsReport );
235 }
236 catch( PARSE_ERROR& pe )
237 {
238 m_errorsReport->Report( wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>%s" ), _( "ERROR:" ), pe.lineNumber,
239 pe.byteIndex, pe.ParseProblem(), wxEmptyString ),
241 m_errorsReport->Flush();
242 return;
243 }
244
245 wxString targetRuleName;
246 bool foundRule = false;
247
248 try
249 {
250 std::string utf8( text.mb_str( wxConvUTF8 ) );
251 DRC_RULES_LEXER lex( utf8, _( "DRC rules" ) );
252
253 struct RuleFrame
254 {
255 int depth;
256 int startLine;
257 int startCol;
258 wxString name;
259 };
260
261 std::vector<RuleFrame> ruleStack;
262 int depth = 0;
263
264 for( ;; )
265 {
266 int tok = lex.NextTok();
267
268 if( tok == DSN_EOF )
269 break;
270
271 if( tok == DSN_LEFT )
272 {
273 int leftLine = lex.CurLineNumber();
274 int leftCol = lex.CurOffset();
275
276 depth++;
277
278 int sub = lex.NextTok();
279
280 if( sub == DRCRULE_T::T_rule )
281 {
282 wxString name;
283 int nameTok = lex.NextTok();
284
285 if( DSNLEXER::IsSymbol( nameTok ) )
286 name = wxString::FromUTF8( lex.CurText() );
287
288 ruleStack.push_back( { depth, leftLine, leftCol, name } );
289 }
290 else if( sub == DSN_LEFT )
291 {
292 depth++;
293 }
294 else if( sub == DSN_RIGHT )
295 {
296 depth--;
297 }
298 else if( sub == DSN_EOF )
299 {
300 break;
301 }
302 }
303 else if( tok == DSN_RIGHT )
304 {
305 if( !ruleStack.empty() && ruleStack.back().depth == depth )
306 {
307 RuleFrame opened = ruleStack.back();
308 ruleStack.pop_back();
309
310 int endLine = lex.CurLineNumber();
311 int endCol = lex.CurOffset();
312
313 bool afterStart = cursorLine > opened.startLine
314 || ( cursorLine == opened.startLine && cursorCol >= opened.startCol );
315 bool beforeEnd = cursorLine < endLine || ( cursorLine == endLine && cursorCol <= endCol );
316
317 if( afterStart && beforeEnd )
318 {
319 targetRuleName = opened.name;
320 foundRule = true;
321 break;
322 }
323 }
324
325 depth--;
326 }
327 }
328 }
329 catch( IO_ERROR& e )
330 {
331 m_errorsReport->Report( wxString::Format( _( "Could not scan rules text: %s" ), e.What() ),
333 m_errorsReport->Flush();
334 return;
335 }
336
337 if( !foundRule )
338 {
339 m_errorsReport->Report( _( "Place the cursor inside a (rule ...) block to show matching "
340 "items." ),
342 m_errorsReport->Flush();
343 return;
344 }
345
346 if( targetRuleName.IsEmpty() )
347 {
348 m_errorsReport->Report( _( "The rule under the cursor has no name; add a name to identify "
349 "it." ),
351 m_errorsReport->Flush();
352 return;
353 }
354
355 std::shared_ptr<DRC_RULE> targetRule;
356
357 for( const auto& rule : rules )
358 {
359 if( rule->m_Name == targetRuleName )
360 {
361 targetRule = rule;
362 break;
363 }
364 }
365
366 if( !targetRule )
367 {
368 m_errorsReport->Report( wxString::Format( _( "Rule '%s' could not be located after "
369 "parsing." ),
370 targetRuleName ),
372 m_errorsReport->Flush();
373 return;
374 }
375
376 if( targetRule->m_Condition && !targetRule->m_Condition->GetExpression().IsEmpty()
377 && !targetRule->m_Condition->Compile( m_errorsReport ) )
378 {
379 m_errorsReport->Flush();
380 return;
381 }
382
383 std::shared_ptr<DRC_ENGINE> engine = m_frame->GetBoard()->GetDesignSettings().m_DRCEngine;
384
385 if( !engine )
386 {
387 m_errorsReport->Report( _( "DRC engine not available." ), RPT_SEVERITY_ERROR );
388 m_errorsReport->Flush();
389 return;
390 }
391
392 std::vector<BOARD_ITEM*> allMatches = engine->GetItemsMatchingRule( targetRule, m_errorsReport );
393
394 std::vector<BOARD_ITEM*> matches;
395
396 for( BOARD_ITEM* item : allMatches )
397 {
398 switch( item->Type() )
399 {
400 case PCB_NETINFO_T:
401 case PCB_GENERATOR_T:
402 case PCB_GROUP_T: continue;
403 default: matches.push_back( item ); break;
404 }
405 }
406
407 m_frame->FocusOnItems( matches );
408
409 m_errorsReport->Report(
410 wxString::Format( _( "Rule '%s': %zu matching item(s)." ), targetRule->m_Name, matches.size() ),
412 m_errorsReport->Flush();
413}
414
415
416void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
417{
418 if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKey() == ' ' )
419 {
420 // This is just a short-cut for do-auto-complete
421 }
422 else
423 {
425 }
426
427 m_textEditor->SearchAnchor();
428
429 int currentPos = m_textEditor->GetCurrentPos();
430 int startPos = 0;
431
432 for( int line = m_textEditor->LineFromPosition( currentPos ); line > 0; line-- )
433 {
434 int lineStart = m_textEditor->PositionFromLine( line );
435 wxString beginning = m_textEditor->GetTextRange( lineStart, lineStart + 10 );
436
437 if( beginning.StartsWith( wxT( "(rule " ) ) )
438 {
439 startPos = lineStart;
440 break;
441 }
442 }
443
444 enum
445 {
446 NO_CONTEXT,
447 STRING,
448 SEXPR_OPEN,
449 SEXPR_TOKEN,
450 SEXPR_STRING,
451 STRUCT_REF
452 };
453
454 auto isDisallowToken =
455 []( const wxString& token ) -> bool
456 {
457 return token == wxT( "blind_via" )
458 || token == wxT( "buried_via" )
459 || token == wxT( "graphic" )
460 || token == wxT( "hole" )
461 || token == wxT( "micro_via" )
462 || token == wxT( "pad" )
463 || token == wxT( "text" )
464 || token == wxT( "through_via" )
465 || token == wxT( "track" )
466 || token == wxT( "via" )
467 || token == wxT( "zone" );
468 };
469
470 auto isConstraintTypeToken =
471 []( const wxString& token ) -> bool
472 {
473 return token == wxT( "annular_width" )
474 || token == wxT( "assertion" )
475 || token == wxT( "clearance" )
476 || token == wxT( "connection_width" )
477 || token == wxT( "courtyard_clearance" )
478 || token == wxT( "diff_pair_gap" )
479 || token == wxT( "diff_pair_uncoupled" )
480 || token == wxT( "disallow" )
481 || token == wxT( "edge_clearance" )
482 || token == wxT( "length" )
483 || token == wxT( "hole_clearance" )
484 || token == wxT( "hole_size" )
485 || token == wxT( "hole_to_hole" )
486 || token == wxT( "min_resolved_spokes" )
487 || token == wxT( "physical_clearance" )
488 || token == wxT( "physical_hole_clearance" )
489 || token == wxT( "silk_clearance" )
490 || token == wxT( "skew" )
491 || token == wxT( "solder_mask_expansion" )
492 || token == wxT( "solder_paste_abs_margin" )
493 || token == wxT( "solder_paste_rel_margin" )
494 || token == wxT( "text_height" )
495 || token == wxT( "text_thickness" )
496 || token == wxT( "thermal_relief_gap" )
497 || token == wxT( "thermal_spoke_width" )
498 || token == wxT( "track_width" )
499 || token == wxT( "track_angle" )
500 || token == wxT( "track_segment_length" )
501 || token == wxT( "via_count" )
502 || token == wxT( "via_diameter" )
503 || token == wxT( "zone_connection" );
504 };
505
506 std::stack<wxString> sexprs;
507 wxString partial;
508 wxString last;
509 wxString constraintType;
510 int context = NO_CONTEXT;
511 int expr_context = NO_CONTEXT;
512
513 for( int i = startPos; i < currentPos; ++i )
514 {
515 wxChar c = m_textEditor->GetCharAt( i );
516
517 if( c == '\\' )
518 {
519 i++; // skip escaped char
520 }
521 else if( context == STRING )
522 {
523 if( c == '"' )
524 {
525 context = NO_CONTEXT;
526 }
527 else
528 {
529 if( expr_context == STRING )
530 {
531 if( c == '\'' )
532 expr_context = NO_CONTEXT;
533 else
534 partial += c;
535 }
536 else if( c == '\'' )
537 {
538 last = partial;
539 partial = wxEmptyString;
540 expr_context = STRING;
541 }
542 else if( c == '.' )
543 {
544 partial = wxEmptyString;
545 expr_context = STRUCT_REF;
546 }
547 else
548 {
549 partial += c;
550 }
551 }
552 }
553 else if( c == '"' )
554 {
555 last = partial;
556 partial = wxEmptyString;
557 context = STRING;
558 }
559 else if( c == '(' )
560 {
561 if( context == SEXPR_OPEN && !partial.IsEmpty() )
562 {
563 m_textEditor->AutoCompCancel();
564 sexprs.push( partial );
565 }
566
567 partial = wxEmptyString;
568 context = SEXPR_OPEN;
569 }
570 else if( c == ')' )
571 {
572 while( !sexprs.empty() && ( sexprs.top() == wxT( "assertion" )
573 || sexprs.top() == wxT( "disallow" )
574 || isDisallowToken( sexprs.top() )
575 || sexprs.top() == wxT( "min_resolved_spokes" )
576 || sexprs.top() == wxT( "zone_connection" ) ) )
577 {
578 sexprs.pop();
579 }
580
581 if( !sexprs.empty() )
582 {
583 // Ignore argument-less tokens
584 if( partial == wxT( "within_diff_pairs" ) )
585 {
586 partial = wxEmptyString;
587 }
588 else
589 {
590 if( sexprs.top() == wxT( "constraint" ) )
591 {
592 constraintType = wxEmptyString;
593 }
594
595 sexprs.pop();
596 }
597 }
598
599 context = NO_CONTEXT;
600 }
601 else if( c == ' ' )
602 {
603 if( context == SEXPR_OPEN && !partial.IsEmpty() )
604 {
605 m_textEditor->AutoCompCancel();
606 sexprs.push( partial );
607
608 if( partial == wxT( "constraint" )
609 || partial == wxT( "layer" )
610 || partial == wxT( "severity" ) )
611 {
612 context = SEXPR_TOKEN;
613 }
614 else if( partial == wxT( "rule" )
615 || partial == wxT( "condition" ) )
616 {
617 context = SEXPR_STRING;
618 }
619 else
620 {
621 context = NO_CONTEXT;
622 }
623
624 partial = wxEmptyString;
625 continue;
626 }
627 else if( partial == wxT( "disallow" )
628 || isDisallowToken( partial )
629 || partial == wxT( "min_resolved_spokes" )
630 || partial == wxT( "zone_connection" ) )
631 {
632 m_textEditor->AutoCompCancel();
633 sexprs.push( partial );
634
635 partial = wxEmptyString;
636 context = SEXPR_TOKEN;
637 continue;
638 }
639 else if( partial == wxT( "assertion" ) )
640 {
641 m_textEditor->AutoCompCancel();
642 sexprs.push( partial );
643
644 partial = wxEmptyString;
645 context = SEXPR_STRING;
646 continue;
647 }
648 else if( isConstraintTypeToken( partial ) )
649 {
650 constraintType = partial;
651 }
652
653 context = NO_CONTEXT;
654 }
655 else
656 {
657 partial += c;
658 }
659 }
660
661 wxString tokens;
662
663 if( context == SEXPR_OPEN )
664 {
665 if( sexprs.empty() )
666 {
667 tokens = wxT( "rule|"
668 "version" );
669 }
670 else if( sexprs.top() == wxT( "rule" ) )
671 {
672 tokens = wxT( "condition|"
673 "constraint|"
674 "layer|"
675 "severity" );
676 }
677 else if( sexprs.top() == wxT( "constraint" ) )
678 {
679 if( constraintType == wxT( "skew" ) )
680 tokens = wxT( "max|min|opt|within_diff_pairs" );
681 else
682 tokens = wxT( "max|min|opt" );
683 }
684 }
685 else if( context == SEXPR_TOKEN )
686 {
687 if( sexprs.empty() )
688 {
689 /* badly formed grammar */
690 }
691 else if( sexprs.top() == wxT( "constraint" ) )
692 {
693 tokens = wxT( "annular_width|"
694 "assertion|"
695 "bridged_mask|"
696 "clearance|"
697 "connection_width|"
698 "courtyard_clearance|"
699 "creepage|"
700 "diff_pair_gap|"
701 "diff_pair_uncoupled|"
702 "disallow|"
703 "edge_clearance|"
704 "length|"
705 "hole_clearance|"
706 "hole_size|"
707 "hole_to_hole|"
708 "min_resolved_spokes|"
709 "physical_clearance|"
710 "physical_hole_clearance|"
711 "silk_clearance|"
712 "skew|"
713 "solder_mask_expansion|"
714 "solder_paste_abs_margin|"
715 "solder_paste_rel_margin|"
716 "text_height|"
717 "text_thickness|"
718 "thermal_relief_gap|"
719 "thermal_spoke_width|"
720 "track_width|"
721 "track_angle|"
722 "track_segment_length|"
723 "via_count|"
724 "via_diameter|"
725 "zone_connection" );
726 }
727 else if( sexprs.top() == wxT( "disallow" ) || isDisallowToken( sexprs.top() ) )
728 {
729 tokens = wxT( "blind_via|"
730 "buried_via|"
731 "footprint|"
732 "graphic|"
733 "hole|"
734 "micro_via|"
735 "pad|"
736 "text|"
737 "through_via|"
738 "track|"
739 "via|"
740 "zone" );
741 }
742 else if( sexprs.top() == wxT( "zone_connection" ) )
743 {
744 tokens = wxT( "none|solid|thermal_reliefs" );
745 }
746 else if( sexprs.top() == wxT( "min_resolved_spokes" ) )
747 {
748 tokens = wxT( "0|1|2|3|4" );
749 }
750 else if( sexprs.top() == wxT( "layer" ) )
751 {
752 tokens = wxT( "inner|outer|\"x\"" );
753 }
754 else if( sexprs.top() == wxT( "severity" ) )
755 {
756 tokens = wxT( "warning|error|ignore|exclusion" );
757 }
758 }
759 else if( context == SEXPR_STRING && !sexprs.empty()
760 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
761 {
762 m_textEditor->AddText( wxT( "\"" ) );
763 }
764 else if( context == STRING && !sexprs.empty()
765 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
766 {
767 if( expr_context == STRUCT_REF )
768 {
770 std::set<wxString> propNames;
771
772 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
773 {
774 const std::vector<PROPERTY_BASE*>& props = propMgr.GetProperties( cls.type );
775
776 for( PROPERTY_BASE* prop : props )
777 {
778 // TODO: It would be nice to replace IsHiddenFromRulesEditor with a nickname
779 // system, so that two different properies don't need to be created. This is
780 // a bigger change than I want to make right now, though.
781 if( prop->IsHiddenFromRulesEditor() )
782 continue;
783
784 wxString ref( prop->Name() );
785 ref.Replace( wxT( " " ), wxT( "_" ) );
786 propNames.insert( ref );
787 }
788 }
789
790 for( const wxString& propName : propNames )
791 tokens += wxT( "|" ) + propName;
792
794
795 for( const wxString& funcSig : functions.GetSignatures() )
796 {
797 if( !funcSig.Contains( "DEPRECATED" ) )
798 tokens += wxT( "|" ) + funcSig;
799 }
800 }
801 else if( expr_context == STRING )
802 {
803 if( m_netClassRegex.Matches( last ) )
804 {
805 BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
806 std::shared_ptr<NET_SETTINGS>& netSettings = bds.m_NetSettings;
807
808 for( const auto& [name, netclass] : netSettings->GetNetclasses() )
809 tokens += wxT( "|" ) + name;
810 }
811 else if( m_netNameRegex.Matches( last ) )
812 {
813 BOARD* board = m_frame->GetBoard();
814
815 for( const wxString& netnameCandidate : board->GetNetClassAssignmentCandidates() )
816 tokens += wxT( "|" ) + netnameCandidate;
817 }
818 else if( m_typeRegex.Matches( last ) )
819 {
820 tokens = wxT( "Bitmap|"
821 "Dimension|"
822 "Footprint|"
823 "Graphic|"
824 "Group|"
825 "Leader|"
826 "Pad|"
827 "Target|"
828 "Text|"
829 "Text Box|"
830 "Track|"
831 "Via|"
832 "Zone" );
833 }
834 else if( m_viaTypeRegex.Matches( last ) )
835 {
836 tokens = wxT( "Through|"
837 "Blind|"
838 "Buried|"
839 "Micro" );
840 }
841 else if( m_padTypeRegex.Matches( last ) )
842 {
843 tokens = wxT( "Through-hole|"
844 "SMD|"
845 "Edge connector|"
846 "NPTH, mechanical" );
847 }
848 else if( m_pinTypeRegex.Matches( last ) )
849 {
850 tokens = wxT( "Input|"
851 "Output|"
852 "Bidirectional|"
853 "Tri-state|"
854 "Passive|"
855 "Free|"
856 "Unspecified|"
857 "Power input|"
858 "Power output|"
859 "Open collector|"
860 "Open emitter|"
861 "Unconnected" );
862 }
863 else if( m_fabPropRegex.Matches( last ) )
864 {
865 tokens = wxT( "None|"
866 "BGA pad|"
867 "Fiducial, global to board|"
868 "Fiducial, local to footprint|"
869 "Test point pad|"
870 "Heatsink pad|"
871 "Castellated pad" );
872 }
873 else if( m_shapeRegex.Matches( last ) )
874 {
875 tokens = wxT( "Segment|"
876 "Rectangle|"
877 "Arc|"
878 "Circle|"
879 "Polygon|"
880 "Bezier" );
881 }
882 else if( m_padShapeRegex.Matches( last ) )
883 {
884 tokens = wxT( "Circle|"
885 "Rectangle|"
886 "Oval|"
887 "Trapezoid|"
888 "Rounded rectangle|"
889 "Chamfered rectangle|"
890 "Custom" );
891 }
892 else if( m_padConnectionsRegex.Matches( last ) )
893 {
894 tokens = wxT( "Inherited|"
895 "None|"
896 "Solid|"
897 "Thermal reliefs|"
898 "Thermal reliefs for PTH" );
899 }
900 else if( m_zoneConnStyleRegex.Matches( last ) )
901 {
902 tokens = wxT( "Inherited|"
903 "None|"
904 "Solid|"
905 "Thermal reliefs" );
906 }
907 else if( m_lineStyleRegex.Matches( last ) )
908 {
909 tokens = wxT( "Default|"
910 "Solid|"
911 "Dashed|"
912 "Dotted|"
913 "Dash-Dot|"
914 "Dash-Dot-Dot" );
915 }
916 else if( m_hJustRegex.Matches( last ) )
917 {
918 tokens = wxT( "Left|"
919 "Center|"
920 "Right" );
921 }
922 else if( m_vJustRegex.Matches( last ) )
923 {
924 tokens = wxT( "Top|"
925 "Center|"
926 "Bottom" );
927 }
928 }
929 }
930
931 if( !tokens.IsEmpty() )
932 m_scintillaTricks->DoAutocomplete( partial, wxSplit( tokens, '|' ) );
933}
934
935
936void PANEL_SETUP_RULES::OnCompile( wxCommandEvent& event )
937{
938 m_errorsReport->Clear();
939
940 try
941 {
942 std::vector<std::shared_ptr<DRC_RULE>> dummyRules;
943
944 std::function<bool( wxString* )> resolver =
945 [&]( wxString* token ) -> bool
946 {
947 if( IsComponentClassSelector( *token ) )
948 return false;
949
950 return m_frame->GetBoard()->ResolveTextVar( token, 0 );
951 };
952
953 wxString rulesText = m_textEditor->GetText();
954 rulesText = m_frame->GetBoard()->ConvertCrossReferencesToKIIDs( rulesText );
955 rulesText = ExpandTextVars( rulesText, &resolver );
956
957 DRC_RULES_PARSER parser( rulesText, _( "DRC rules" ) );
958
959 parser.Parse( dummyRules, m_errorsReport );
960 checkPlausibility( dummyRules );
961 }
962 catch( PARSE_ERROR& pe )
963 {
964 m_errorsReport->Report( wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>%s" ),
965 _( "ERROR:" ),
966 pe.lineNumber,
967 pe.byteIndex,
968 pe.ParseProblem(),
969 wxEmptyString ),
971 }
972
973 m_errorsReport->Flush();
974}
975
976
977void PANEL_SETUP_RULES::checkPlausibility( const std::vector<std::shared_ptr<DRC_RULE>>& aRules )
978{
979 BOARD* board = m_frame->GetBoard();
981 LSET enabledLayers = board->GetEnabledLayers();
982
983 // Key by (condition, layerSource) so rules with different layer scopes are considered distinct
984 std::map<std::pair<wxString, wxString>, wxString> seenConditions;
985 std::regex netclassPattern( "NetClass\\s*[!=]=\\s*'\"?([^\"\\s]+)'\"?" );
986
987 for( const auto& rule : aRules )
988 {
989 wxString condition;
990
991 if( rule->m_Condition )
992 condition = rule->m_Condition->GetExpression();
993
994 condition.Trim( true ).Trim( false );
995
996 auto key = std::make_pair( condition, rule->m_LayerSource );
997
998 if( seenConditions.count( key ) )
999 {
1000 m_errorsReport->Report( wxString::Format( _( "Rules '%s' and '%s' share the same condition." ),
1001 rule->m_Name,
1002 seenConditions[key] ),
1004 }
1005 else
1006 {
1007 seenConditions[key] = rule->m_Name;
1008 }
1009
1010 std::string condUtf8 = condition.ToStdString();
1011 std::sregex_iterator it( condUtf8.begin(), condUtf8.end(), netclassPattern );
1012 std::sregex_iterator end;
1013
1014 for( ; it != end; ++it )
1015 {
1016 wxString ncName = wxString::FromUTF8( ( *it )[1].str() );
1017
1018 if( !bds.m_NetSettings->HasNetclass( ncName ) )
1019 {
1020 m_errorsReport->Report( wxString::Format( _( "Rule '%s' references undefined netclass '%s'." ),
1021 rule->m_Name,
1022 ncName ),
1024 }
1025 }
1026
1027 const bool isInner = rule->m_LayerSource.IsSameAs( wxT( "'inner'" ), false );
1028 const bool isOuter = rule->m_LayerSource.IsSameAs( wxT( "'outer'" ), false );
1029
1030 if( !rule->m_LayerSource.IsEmpty() && !isInner && !isOuter )
1031 {
1032 LSET invalid = rule->m_LayerCondition & ~enabledLayers;
1033
1034 if( invalid.any() )
1035 {
1036 wxString badLayers;
1037
1038 for( PCB_LAYER_ID layer : invalid.Seq() )
1039 {
1040 if( !badLayers.IsEmpty() )
1041 badLayers += ", ";
1042
1043 badLayers += board->GetLayerName( layer );
1044 }
1045
1046 m_errorsReport->Report( wxString::Format( _( "Rule '%s' references undefined layer(s): %s." ),
1047 rule->m_Name,
1048 badLayers ),
1050 }
1051 }
1052 }
1053}
1054
1055
1056void PANEL_SETUP_RULES::OnErrorLinkClicked( wxHtmlLinkEvent& event )
1057{
1058 wxString link = event.GetLinkInfo().GetHref();
1059 wxArrayString parts;
1060 long line = 0, offset = 0;
1061
1062 wxStringSplit( link, parts, ':' );
1063
1064 if( parts.size() > 1 )
1065 {
1066 parts[0].ToLong( &line );
1067 parts[1].ToLong( &offset );
1068 }
1069
1070 int pos = m_textEditor->PositionFromLine( line - 1 ) + ( offset - 1 );
1071
1072 m_textEditor->GotoPos( pos );
1073
1074 m_textEditor->SetFocus();
1075}
1076
1077
1079{
1080 if( !m_frame->GetBoard() )
1081 return true;
1082
1083 wxFileName rulesFile( m_frame->GetBoard()->GetDesignRulesPath() );
1084
1085 if( rulesFile.FileExists() )
1086 {
1087 wxTextFile file( rulesFile.GetFullPath() );
1088
1089 if( file.Open() )
1090 {
1091 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
1092 {
1094 m_textEditor->AddText( str << '\n' );
1095 }
1096
1097 m_textEditor->EmptyUndoBuffer();
1098
1099 wxCommandEvent dummy;
1100 OnCompile( dummy );
1101 }
1102 }
1103 else
1104 {
1105 m_textEditor->AddText( wxT( "(version 2)\n" ) );
1106 }
1107
1108 m_originalText = m_textEditor->GetText();
1109
1110 if( m_frame->Prj().IsNullProject() )
1111 {
1112 m_textEditor->ClearAll();
1113 m_textEditor->AddText( _( "Design rules cannot be added without a project" ) );
1114 m_textEditor->Disable();
1115 }
1116
1117 return true;
1118}
1119
1120
1122{
1123 if( m_originalText == m_textEditor->GetText() )
1124 return true;
1125
1126 if( m_frame->Prj().IsNullProject() )
1127 return true;
1128
1129 if( !m_frame->GetBoard() )
1130 return true;
1131
1132 wxString rulesFilepath = m_frame->GetBoard()->GetDesignRulesPath();
1133
1134 wxString content = m_textEditor->GetText();
1135 std::string utf8 = std::string( content.mb_str( wxConvUTF8 ) );
1136 wxString writeError;
1137
1138 if( !KIPLATFORM::IO::AtomicWriteFile( rulesFilepath, utf8.data(), utf8.size(), &writeError ) )
1139 {
1140 wxLogError( _( "Cannot save design rules to '%s': %s" ), rulesFilepath, writeError );
1141 return false;
1142 }
1143
1144 try
1145 {
1146 m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine( rulesFilepath );
1147 return true;
1148 }
1149 catch( PARSE_ERROR& )
1150 {
1151 // Don't lock them in to the Setup dialog if they have bad rules. They've already
1152 // saved them so we can allow an exit.
1153 return true;
1154 }
1155}
1156
1157
1158void PANEL_SETUP_RULES::OnSyntaxHelp( wxHyperlinkEvent& aEvent )
1159{
1160 if( m_helpWindow )
1161 {
1162 m_helpWindow->ShowModeless();
1163 return;
1164 }
1165 std::vector<wxString> msg;
1166 msg.clear();
1167
1168 wxString t =
1170 ;
1171 msg.emplace_back( t );
1172 t =
1174 ;
1175 msg.emplace_back( t );
1176 t =
1178 ;
1179 msg.emplace_back( t );
1180 t =
1182 ;
1183 msg.emplace_back( t );
1184 t =
1186 ;
1187 msg.emplace_back( t );
1188 t =
1190 ;
1191 msg.emplace_back( t );
1192 t =
1194 ;
1195 msg.emplace_back( t );
1196 t =
1198 ;
1199 msg.emplace_back( t );
1200 t =
1202 ;
1203 msg.emplace_back( t );
1204 t =
1206 ;
1207 msg.emplace_back( t );
1208
1209 wxString msg_txt = wxEmptyString;
1210
1211 for( wxString i : msg )
1212 msg_txt << wxGetTranslation( i );
1213
1214#ifdef __WXMAC__
1215 msg_txt.Replace( wxT( "Ctrl+" ), wxT( "Cmd+" ) );
1216#endif
1217 const wxString& msGg_txt = msg_txt;
1218
1219 m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Syntax Help" ) );
1220 m_helpWindow->SetDialogSizeInDU( 420, 320 );
1221
1222 wxString html_txt = wxEmptyString;
1223 ConvertMarkdown2Html( msGg_txt, html_txt );
1224
1225 html_txt.Replace( wxS( "<td" ), wxS( "<td valign=top" ) );
1226 m_helpWindow->AddHTML_Text( html_txt );
1227
1228 m_helpWindow->ShowModeless();
1229}
1230
1231
1233{
1234 if( !m_frame->Prj().IsNullProject() )
1235 {
1236 wxFileName relFile = aBoard->GetFileName();
1237 relFile.SetExt( FILEEXT::DesignRulesFileExtension );
1238
1239 wxFileName absFile( aBoard->GetProject()->AbsolutePath( relFile.GetFullName() ) );
1240
1241 if( absFile.FileExists() )
1242 {
1243 wxTextFile file( absFile.GetFullPath() );
1244
1245 if( file.Open() )
1246 {
1247 m_textEditor->ClearAll();
1248
1249 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
1250 {
1252 m_textEditor->AddText( str << '\n' );
1253 }
1254
1255 m_textEditor->EmptyUndoBuffer();
1256
1257 wxCommandEvent dummy;
1258 OnCompile( dummy );
1259 }
1260 }
1261 }
1262}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:83
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
std::set< wxString > GetNetClassAssignmentCandidates() const
Return the set of netname candidates for netclass assignment.
Definition board.cpp:2915
const wxString & GetFileName() const
Definition board.h:410
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:802
PROJECT * GetProject() const
Definition board.h:662
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1158
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1043
APPEARANCE m_Appearance
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
static bool IsSymbol(int aTok)
Test a token to see if it is a symbol.
Definition dsnlexer.cpp:323
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition lset.cpp:309
bool HasNetclass(const wxString &netclassName) const
Determines if the given netclass exists.
void SetModified()
static PAGED_DIALOG * GetDialog(wxWindow *aWindow)
STD_BITMAP_BUTTON * m_compileButton
wxStyledTextCtrl * m_textEditor
PANEL_SETUP_RULES_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxTAB_TRAVERSAL, const wxString &name=wxEmptyString)
WX_HTML_REPORT_BOX * m_errorsReport
bool TransferDataToWindow() override
void OnErrorLinkClicked(wxHtmlLinkEvent &event) override
void checkPlausibility(const std::vector< std::shared_ptr< DRC_RULE > > &aRules)
void onScintillaCharAdded(wxStyledTextEvent &aEvent)
void ImportSettingsFrom(BOARD *aBoard)
PCB_EDIT_FRAME * m_frame
void OnContextMenu(wxMouseEvent &event) override
HTML_MESSAGE_BOX * m_helpWindow
void OnCompile(wxCommandEvent &event) override
bool TransferDataFromWindow() override
void OnSyntaxHelp(wxHyperlinkEvent &aEvent) override
PANEL_SETUP_RULES(wxWindow *aParentWindow, PCB_EDIT_FRAME *aFrame)
void onCharHook(wxKeyEvent &aEvent)
SCINTILLA_TRICKS * m_scintillaTricks
const wxArrayString GetSignatures() const
static PCBEXPR_BUILTIN_FUNCTIONS & Instance()
The main frame for Pcbnew.
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:562
virtual const wxString AbsolutePath(const wxString &aFileName) const
Fix up aFileName if it is relative to the project's directory to be an absolute path and filename.
Definition project.cpp:407
Provide class metadata.Helper macro to map type hashes to names.
const std::vector< PROPERTY_BASE * > & GetProperties(TYPE_ID aType) const
Return all properties for a specific type.
CLASSES_INFO GetAllClasses()
static PROPERTY_MANAGER & Instance()
Add cut/copy/paste, dark theme, autocomplete and brace highlighting to a wxStyleTextCtrl instance.
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, int aFlags)
Definition common.cpp:59
The common library.
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition confirm.cpp:274
This file is part of the common library.
bool IsComponentClassSelector(const wxString &aToken)
${Class:X} inside a DRC rule is a component-class selector consumed by testFootprintSelector(),...
Definition drc_rule.cpp:30
@ DSN_LEFT
Definition dsnlexer.h:63
@ DSN_RIGHT
Definition dsnlexer.h:62
@ DSN_EOF
Definition dsnlexer.h:65
#define _(s)
static FILENAME_RESOLVER * resolver
static const std::string DesignRulesFileExtension
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
bool AtomicWriteFile(const wxString &aTargetPath, const void *aData, size_t aSize, wxString *aError=nullptr)
Writes aData to aTargetPath via a sibling temp file, fsyncs the data and directory,...
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
@ RPT_SEVERITY_ACTION
std::vector< FAB_LAYER_COLOR > dummy
bool ConvertSmartQuotesAndDashes(wxString *aString)
Convert curly quotes and em/en dashes to straight quotes and dashes.
void wxStringSplit(const wxString &aText, wxArrayString &aStrings, wxChar aSplitter)
Split aString to a string list separated at aSplitter.
void ConvertMarkdown2Html(const wxString &aMarkdownInput, wxString &aHtmlOutput)
A filename or source description, a problem input line, a line number, a byte offset,...
int lineNumber
at which line number, 1 based index.
const wxString ParseProblem()
int byteIndex
at which byte offset within the line, 1 based index
VECTOR2I end
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:84
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:104
@ PCB_NETINFO_T
class NETINFO_ITEM, a description of a net
Definition typeinfo.h:103
Definition of file extensions used in Kicad.