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 <kiplatform/io.h>
25#include <pcb_edit_frame.h>
26#include <pcbexpr_evaluator.h>
27#include <board.h>
29#include <drc/drc_engine.h>
32#include <project.h>
33#include <string_utils.h>
34#include <tool/tool_manager.h>
35#include <panel_setup_rules.h>
38#include <scintilla_tricks.h>
39#include <drc/drc_rule.h>
41#include <drc/drc_rule_parser.h>
42#include <tools/drc_tool.h>
43#include <pgm_base.h>
45#include <regex>
46#include <unordered_map>
47#include <properties/property.h>
49
50PANEL_SETUP_RULES::PANEL_SETUP_RULES( wxWindow* aParentWindow, PCB_EDIT_FRAME* aFrame ) :
51 PANEL_SETUP_RULES_BASE( aParentWindow ),
52 m_frame( aFrame ),
53 m_scintillaTricks( nullptr ),
54 m_helpWindow( nullptr )
55{
56 m_scintillaTricks = new SCINTILLA_TRICKS( m_textEditor, wxT( "()" ), false,
57 // onAcceptFn
58 [this]( wxKeyEvent& aEvent )
59 {
60 wxPostEvent( PAGED_DIALOG::GetDialog( this ),
61 wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
62 },
63 // onCharFn
64 [this]( wxStyledTextEvent& aEvent )
65 {
66 onScintillaCharAdded( aEvent );
67 } );
68
69 m_textEditor->AutoCompSetSeparator( '|' );
70
71 m_netClassRegex.Compile( "^NetClass\\s*[!=]=\\s*$", wxRE_ADVANCED );
72 m_netNameRegex.Compile( "^NetName\\s*[!=]=\\s*$", wxRE_ADVANCED );
73 m_typeRegex.Compile( "^Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
74 m_viaTypeRegex.Compile( "^Via_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
75 m_padTypeRegex.Compile( "^Pad_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
76 m_pinTypeRegex.Compile( "^Pin_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
77 m_fabPropRegex.Compile( "^Fabrication_Property\\s*[!=]=\\s*$", wxRE_ADVANCED );
78 m_shapeRegex.Compile( "^Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
79 m_padShapeRegex.Compile( "^Pad_Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
80 m_padConnectionsRegex.Compile( "^Pad_Connections\\s*[!=]=\\s*$", wxRE_ADVANCED );
81 m_zoneConnStyleRegex.Compile( "^Zone_Connection_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
82 m_lineStyleRegex.Compile( "^Line_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
83 m_hJustRegex.Compile( "^Horizontal_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
84 m_vJustRegex.Compile( "^Vertical_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
85
87
88 m_textEditor->SetZoom( Pgm().GetCommonSettings()->m_Appearance.text_editor_zoom );
89
90 m_textEditor->UsePopUp( 0 );
91 m_textEditor->Bind( wxEVT_CHAR_HOOK, &PANEL_SETUP_RULES::onCharHook, this );
92}
93
94
96{
97 m_textEditor->Unbind( wxEVT_CHAR_HOOK, &PANEL_SETUP_RULES::onCharHook, this );
99
100 delete m_scintillaTricks;
101 m_scintillaTricks = nullptr;
102
103 if( m_helpWindow )
104 m_helpWindow->Destroy();
105};
106
107
108void PANEL_SETUP_RULES::onCharHook( wxKeyEvent& aEvent )
109{
110 // Route Escape through the dialog's cancel handler so unsaved rules get a discard prompt
111 if( aEvent.GetKeyCode() == WXK_ESCAPE && !m_textEditor->AutoCompActive() )
112 {
113 wxPostEvent( PAGED_DIALOG::GetDialog( this ), wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_CANCEL ) );
114 return;
115 }
116
117 aEvent.Skip();
118}
119
120
122{
123 if( m_frame->Prj().IsNullProject() || !m_frame->GetBoard() )
124 return false;
125
126 return m_originalText != m_textEditor->GetText();
127}
128
129
130void PANEL_SETUP_RULES::OnContextMenu(wxMouseEvent &event)
131{
132 wxMenu menu;
133
134 menu.Append( wxID_UNDO, _( "Undo" ) );
135 menu.Append( wxID_REDO, _( "Redo" ) );
136
137 menu.AppendSeparator();
138
139 menu.Append( 1, _( "Cut" ) ); // Don't use wxID_CUT, wxID_COPY, etc. On Mac (at least),
140 menu.Append( 2, _( "Copy" ) ); // wxWidgets never delivers them to us.
141 menu.Append( 3, _( "Paste" ) );
142 menu.Append( 4, _( "Delete" ) );
143
144 menu.AppendSeparator();
145
146 menu.Append( 5, _( "Select All" ) );
147
148 menu.AppendSeparator();
149
150 menu.Append( wxID_ZOOM_IN, _( "Zoom In" ) );
151 menu.Append( wxID_ZOOM_OUT, _( "Zoom Out" ) );
152
153 menu.AppendSeparator();
154
155 menu.Append( 6, _( "Show Matching Items" ) );
156
157 switch( GetPopupMenuSelectionFromUser( menu ) )
158 {
159 case wxID_UNDO:
160 m_textEditor->Undo();
161 break;
162 case wxID_REDO:
163 m_textEditor->Redo();
164 break;
165
166 case 1:
167 m_textEditor->Cut();
168 break;
169 case 2:
170 m_textEditor->Copy();
171 break;
172 case 3:
173 m_textEditor->Paste();
174 break;
175 case 4:
176 {
177 long from, to;
178 m_textEditor->GetSelection( &from, &to );
179
180 if( to > from )
181 m_textEditor->DeleteRange( from, to );
182
183 break;
184 }
185
186 case 5:
187 m_textEditor->SelectAll();
188 break;
189
190 case wxID_ZOOM_IN:
191 m_textEditor->ZoomIn();
192 break;
193 case wxID_ZOOM_OUT:
194 m_textEditor->ZoomOut();
195 break;
196
197 case 6: OnShowMatching(); break;
198 }
199}
200
201
203{
204 m_errorsReport->Clear();
205
206 if( m_frame->Prj().IsNullProject() )
207 {
208 m_errorsReport->Report( _( "Cannot show matching items without an open project." ), RPT_SEVERITY_ERROR );
209 m_errorsReport->Flush();
210 return;
211 }
212
213 const wxString text = m_textEditor->GetText();
214 const int cursorPos = m_textEditor->GetCurrentPos();
215 const int cursorLine = m_textEditor->LineFromPosition( cursorPos ) + 1;
216 const int cursorCol = cursorPos - m_textEditor->PositionFromLine( cursorLine - 1 ) + 1;
217
218 std::vector<std::shared_ptr<DRC_RULE>> rules;
219
220 try
221 {
222 std::function<bool( wxString* )> resolver =
223 [&]( wxString* token ) -> bool
224 {
225 if( IsComponentClassSelector( *token ) )
226 return false;
227
228 return m_frame->GetBoard()->ResolveTextVar( token, 0 );
229 };
230
231 wxString rulesText = m_frame->GetBoard()->ConvertCrossReferencesToKIIDs( text );
232 rulesText = ExpandTextVars( rulesText, &resolver, RESOLVED );
233
234 DRC_RULES_PARSER parser( rulesText, _( "DRC rules" ) );
235 parser.Parse( rules, m_errorsReport );
236 }
237 catch( PARSE_ERROR& pe )
238 {
239 m_errorsReport->Report( wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>%s" ), _( "ERROR:" ), pe.lineNumber,
240 pe.byteIndex, pe.ParseProblem(), wxEmptyString ),
242 m_errorsReport->Flush();
243 return;
244 }
245
246 wxString targetRuleName;
247 bool foundRule = false;
248
249 try
250 {
251 std::string utf8( text.mb_str( wxConvUTF8 ) );
252 DRC_RULES_LEXER lex( utf8, _( "DRC rules" ) );
253
254 struct RuleFrame
255 {
256 int depth;
257 int startLine;
258 int startCol;
259 wxString name;
260 };
261
262 std::vector<RuleFrame> ruleStack;
263 int depth = 0;
264
265 for( ;; )
266 {
267 int tok = lex.NextTok();
268
269 if( tok == DSN_EOF )
270 break;
271
272 if( tok == DSN_LEFT )
273 {
274 int leftLine = lex.CurLineNumber();
275 int leftCol = lex.CurOffset();
276
277 depth++;
278
279 int sub = lex.NextTok();
280
281 if( sub == DRCRULE_T::T_rule )
282 {
283 wxString name;
284 int nameTok = lex.NextTok();
285
286 if( DSNLEXER::IsSymbol( nameTok ) )
287 name = wxString::FromUTF8( lex.CurText() );
288
289 ruleStack.push_back( { depth, leftLine, leftCol, name } );
290 }
291 else if( sub == DSN_LEFT )
292 {
293 depth++;
294 }
295 else if( sub == DSN_RIGHT )
296 {
297 depth--;
298 }
299 else if( sub == DSN_EOF )
300 {
301 break;
302 }
303 }
304 else if( tok == DSN_RIGHT )
305 {
306 if( !ruleStack.empty() && ruleStack.back().depth == depth )
307 {
308 RuleFrame opened = ruleStack.back();
309 ruleStack.pop_back();
310
311 int endLine = lex.CurLineNumber();
312 int endCol = lex.CurOffset();
313
314 bool afterStart = cursorLine > opened.startLine
315 || ( cursorLine == opened.startLine && cursorCol >= opened.startCol );
316 bool beforeEnd = cursorLine < endLine || ( cursorLine == endLine && cursorCol <= endCol );
317
318 if( afterStart && beforeEnd )
319 {
320 targetRuleName = opened.name;
321 foundRule = true;
322 break;
323 }
324 }
325
326 depth--;
327 }
328 }
329 }
330 catch( IO_ERROR& e )
331 {
332 m_errorsReport->Report( wxString::Format( _( "Could not scan rules text: %s" ), e.What() ),
334 m_errorsReport->Flush();
335 return;
336 }
337
338 if( !foundRule )
339 {
340 m_errorsReport->Report( _( "Place the cursor inside a (rule ...) block to show matching "
341 "items." ),
343 m_errorsReport->Flush();
344 return;
345 }
346
347 if( targetRuleName.IsEmpty() )
348 {
349 m_errorsReport->Report( _( "The rule under the cursor has no name; add a name to identify "
350 "it." ),
352 m_errorsReport->Flush();
353 return;
354 }
355
356 std::shared_ptr<DRC_RULE> targetRule;
357
358 for( const auto& rule : rules )
359 {
360 if( rule->m_Name == targetRuleName )
361 {
362 targetRule = rule;
363 break;
364 }
365 }
366
367 if( !targetRule )
368 {
369 m_errorsReport->Report( wxString::Format( _( "Rule '%s' could not be located after "
370 "parsing." ),
371 targetRuleName ),
373 m_errorsReport->Flush();
374 return;
375 }
376
377 if( targetRule->m_Condition && !targetRule->m_Condition->GetExpression().IsEmpty()
378 && !targetRule->m_Condition->Compile( m_errorsReport ) )
379 {
380 m_errorsReport->Flush();
381 return;
382 }
383
384 std::shared_ptr<DRC_ENGINE> engine = m_frame->GetBoard()->GetDesignSettings().m_DRCEngine;
385
386 if( !engine )
387 {
388 m_errorsReport->Report( _( "DRC engine not available." ), RPT_SEVERITY_ERROR );
389 m_errorsReport->Flush();
390 return;
391 }
392
393 std::vector<BOARD_ITEM*> allMatches = engine->GetItemsMatchingRule( targetRule, m_errorsReport );
394
395 std::vector<BOARD_ITEM*> matches;
396
397 for( BOARD_ITEM* item : allMatches )
398 {
399 switch( item->Type() )
400 {
401 case PCB_NETINFO_T:
402 case PCB_GENERATOR_T:
403 case PCB_GROUP_T: continue;
404 default: matches.push_back( item ); break;
405 }
406 }
407
408 m_frame->FocusOnItems( matches );
409
410 m_errorsReport->Report(
411 wxString::Format( _( "Rule '%s': %zu matching item(s)." ), targetRule->m_Name, matches.size() ),
413 m_errorsReport->Flush();
414}
415
416
417void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
418{
419 if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKey() == ' ' )
420 {
421 // This is just a short-cut for do-auto-complete
422 }
423 else
424 {
426 }
427
428 m_textEditor->SearchAnchor();
429
430 int currentPos = m_textEditor->GetCurrentPos();
431 int startPos = 0;
432
433 for( int line = m_textEditor->LineFromPosition( currentPos ); line > 0; line-- )
434 {
435 int lineStart = m_textEditor->PositionFromLine( line );
436 wxString beginning = m_textEditor->GetTextRange( lineStart, lineStart + 10 );
437
438 if( beginning.StartsWith( wxT( "(rule " ) ) )
439 {
440 startPos = lineStart;
441 break;
442 }
443 }
444
445 enum
446 {
447 NO_CONTEXT,
448 STRING,
449 SEXPR_OPEN,
450 SEXPR_TOKEN,
451 SEXPR_STRING,
452 STRUCT_REF
453 };
454
455 auto isDisallowToken =
456 []( const wxString& token ) -> bool
457 {
458 return token == wxT( "blind_via" )
459 || token == wxT( "buried_via" )
460 || token == wxT( "graphic" )
461 || token == wxT( "hole" )
462 || token == wxT( "micro_via" )
463 || token == wxT( "pad" )
464 || token == wxT( "text" )
465 || token == wxT( "through_via" )
466 || token == wxT( "track" )
467 || token == wxT( "via" )
468 || token == wxT( "zone" );
469 };
470
471 auto isConstraintTypeToken =
472 []( const wxString& token ) -> bool
473 {
474 return token == wxT( "annular_width" )
475 || token == wxT( "assertion" )
476 || token == wxT( "clearance" )
477 || token == wxT( "connection_width" )
478 || token == wxT( "courtyard_clearance" )
479 || token == wxT( "diff_pair_gap" )
480 || token == wxT( "diff_pair_uncoupled" )
481 || token == wxT( "disallow" )
482 || token == wxT( "edge_clearance" )
483 || token == wxT( "length" )
484 || token == wxT( "hole_clearance" )
485 || token == wxT( "hole_size" )
486 || token == wxT( "hole_to_hole" )
487 || token == wxT( "min_resolved_spokes" )
488 || token == wxT( "physical_clearance" )
489 || token == wxT( "physical_hole_clearance" )
490 || token == wxT( "silk_clearance" )
491 || token == wxT( "skew" )
492 || token == wxT( "solder_mask_expansion" )
493 || token == wxT( "solder_paste_abs_margin" )
494 || token == wxT( "solder_paste_rel_margin" )
495 || token == wxT( "text_height" )
496 || token == wxT( "text_thickness" )
497 || token == wxT( "thermal_relief_gap" )
498 || token == wxT( "thermal_spoke_width" )
499 || token == wxT( "track_width" )
500 || token == wxT( "track_angle" )
501 || token == wxT( "track_segment_length" )
502 || token == wxT( "via_count" )
503 || token == wxT( "via_diameter" )
504 || token == wxT( "zone_connection" );
505 };
506
507 std::stack<wxString> sexprs;
508 wxString partial;
509 wxString last;
510 wxString constraintType;
511 int context = NO_CONTEXT;
512 int expr_context = NO_CONTEXT;
513
514 for( int i = startPos; i < currentPos; ++i )
515 {
516 wxChar c = m_textEditor->GetCharAt( i );
517
518 if( c == '\\' )
519 {
520 i++; // skip escaped char
521 }
522 else if( context == STRING )
523 {
524 if( c == '"' )
525 {
526 context = NO_CONTEXT;
527 }
528 else
529 {
530 if( expr_context == STRING )
531 {
532 if( c == '\'' )
533 expr_context = NO_CONTEXT;
534 else
535 partial += c;
536 }
537 else if( c == '\'' )
538 {
539 last = partial;
540 partial = wxEmptyString;
541 expr_context = STRING;
542 }
543 else if( c == '.' )
544 {
545 partial = wxEmptyString;
546 expr_context = STRUCT_REF;
547 }
548 else
549 {
550 partial += c;
551 }
552 }
553 }
554 else if( c == '"' )
555 {
556 last = partial;
557 partial = wxEmptyString;
558 context = STRING;
559 }
560 else if( c == '(' )
561 {
562 if( context == SEXPR_OPEN && !partial.IsEmpty() )
563 {
564 m_textEditor->AutoCompCancel();
565 sexprs.push( partial );
566 }
567
568 partial = wxEmptyString;
569 context = SEXPR_OPEN;
570 }
571 else if( c == ')' )
572 {
573 while( !sexprs.empty() && ( sexprs.top() == wxT( "assertion" )
574 || sexprs.top() == wxT( "disallow" )
575 || isDisallowToken( sexprs.top() )
576 || sexprs.top() == wxT( "min_resolved_spokes" )
577 || sexprs.top() == wxT( "zone_connection" ) ) )
578 {
579 sexprs.pop();
580 }
581
582 if( !sexprs.empty() )
583 {
584 // Ignore argument-less tokens
585 if( partial == wxT( "within_diff_pairs" ) )
586 {
587 partial = wxEmptyString;
588 }
589 else
590 {
591 if( sexprs.top() == wxT( "constraint" ) )
592 {
593 constraintType = wxEmptyString;
594 }
595
596 sexprs.pop();
597 }
598 }
599
600 context = NO_CONTEXT;
601 }
602 else if( c == ' ' )
603 {
604 if( context == SEXPR_OPEN && !partial.IsEmpty() )
605 {
606 m_textEditor->AutoCompCancel();
607 sexprs.push( partial );
608
609 if( partial == wxT( "constraint" )
610 || partial == wxT( "layer" )
611 || partial == wxT( "severity" ) )
612 {
613 context = SEXPR_TOKEN;
614 }
615 else if( partial == wxT( "rule" )
616 || partial == wxT( "condition" ) )
617 {
618 context = SEXPR_STRING;
619 }
620 else
621 {
622 context = NO_CONTEXT;
623 }
624
625 partial = wxEmptyString;
626 continue;
627 }
628 else if( partial == wxT( "disallow" )
629 || isDisallowToken( partial )
630 || partial == wxT( "min_resolved_spokes" )
631 || partial == wxT( "zone_connection" ) )
632 {
633 m_textEditor->AutoCompCancel();
634 sexprs.push( partial );
635
636 partial = wxEmptyString;
637 context = SEXPR_TOKEN;
638 continue;
639 }
640 else if( partial == wxT( "assertion" ) )
641 {
642 m_textEditor->AutoCompCancel();
643 sexprs.push( partial );
644
645 partial = wxEmptyString;
646 context = SEXPR_STRING;
647 continue;
648 }
649 else if( isConstraintTypeToken( partial ) )
650 {
651 constraintType = partial;
652 }
653
654 context = NO_CONTEXT;
655 }
656 else
657 {
658 partial += c;
659 }
660 }
661
662 wxString tokens;
663
664 if( context == SEXPR_OPEN )
665 {
666 if( sexprs.empty() )
667 {
668 tokens = wxT( "rule|"
669 "version" );
670 }
671 else if( sexprs.top() == wxT( "rule" ) )
672 {
673 tokens = wxT( "condition|"
674 "constraint|"
675 "layer|"
676 "severity" );
677 }
678 else if( sexprs.top() == wxT( "constraint" ) )
679 {
680 if( constraintType == wxT( "skew" ) )
681 tokens = wxT( "max|min|opt|within_diff_pairs" );
682 else
683 tokens = wxT( "max|min|opt" );
684 }
685 }
686 else if( context == SEXPR_TOKEN )
687 {
688 if( sexprs.empty() )
689 {
690 /* badly formed grammar */
691 }
692 else if( sexprs.top() == wxT( "constraint" ) )
693 {
694 tokens = wxT( "annular_width|"
695 "assertion|"
696 "bridged_mask|"
697 "clearance|"
698 "connection_width|"
699 "courtyard_clearance|"
700 "creepage|"
701 "diff_pair_gap|"
702 "diff_pair_uncoupled|"
703 "disallow|"
704 "edge_clearance|"
705 "length|"
706 "hole_clearance|"
707 "hole_size|"
708 "hole_to_hole|"
709 "min_resolved_spokes|"
710 "physical_clearance|"
711 "physical_hole_clearance|"
712 "silk_clearance|"
713 "skew|"
714 "solder_mask_expansion|"
715 "solder_paste_abs_margin|"
716 "solder_paste_rel_margin|"
717 "text_height|"
718 "text_thickness|"
719 "thermal_relief_gap|"
720 "thermal_spoke_width|"
721 "track_width|"
722 "track_angle|"
723 "track_segment_length|"
724 "via_count|"
725 "via_diameter|"
726 "zone_connection" );
727 }
728 else if( sexprs.top() == wxT( "disallow" ) || isDisallowToken( sexprs.top() ) )
729 {
730 tokens = wxT( "blind_via|"
731 "buried_via|"
732 "footprint|"
733 "graphic|"
734 "hole|"
735 "micro_via|"
736 "pad|"
737 "text|"
738 "through_via|"
739 "track|"
740 "via|"
741 "zone" );
742 }
743 else if( sexprs.top() == wxT( "zone_connection" ) )
744 {
745 tokens = wxT( "none|solid|thermal_reliefs" );
746 }
747 else if( sexprs.top() == wxT( "min_resolved_spokes" ) )
748 {
749 tokens = wxT( "0|1|2|3|4" );
750 }
751 else if( sexprs.top() == wxT( "layer" ) )
752 {
753 tokens = wxT( "inner|outer|\"x\"" );
754 }
755 else if( sexprs.top() == wxT( "severity" ) )
756 {
757 tokens = wxT( "warning|error|ignore|exclusion" );
758 }
759 }
760 else if( context == SEXPR_STRING && !sexprs.empty()
761 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
762 {
763 m_textEditor->AddText( wxT( "\"" ) );
764 }
765 else if( context == STRING && !sexprs.empty()
766 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
767 {
768 if( expr_context == STRUCT_REF )
769 {
771 std::set<wxString> propNames;
772
773 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
774 {
775 const std::vector<PROPERTY_BASE*>& props = propMgr.GetProperties( cls.type );
776
777 for( PROPERTY_BASE* prop : props )
778 {
779 // TODO: It would be nice to replace IsHiddenFromRulesEditor with a nickname
780 // system, so that two different properies don't need to be created. This is
781 // a bigger change than I want to make right now, though.
782 if( prop->IsHiddenFromRulesEditor() )
783 continue;
784
785 wxString ref( prop->Name() );
786 ref.Replace( wxT( " " ), wxT( "_" ) );
787 propNames.insert( ref );
788 }
789 }
790
791 for( const wxString& propName : propNames )
792 tokens += wxT( "|" ) + propName;
793
795
796 for( const wxString& funcSig : functions.GetSignatures() )
797 {
798 if( !funcSig.Contains( "DEPRECATED" ) )
799 tokens += wxT( "|" ) + funcSig;
800 }
801 }
802 else if( expr_context == STRING )
803 {
804 if( m_netClassRegex.Matches( last ) )
805 {
806 std::shared_ptr<NET_SETTINGS>& netSettings = m_frame->GetBoard()->GetDesignSettings().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, RESOLVED );
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();
980 LSET enabledLayers = board->GetEnabledLayers();
981
982 // Key by (condition, layerSource) so rules with different layer scopes are considered distinct
983 std::map<std::pair<wxString, wxString>, wxString> seenConditions;
984 std::regex netclassPattern( "NetClass\\s*[!=]=\\s*'\"?([^\"\\s]+)'\"?" );
985
986 for( const auto& rule : aRules )
987 {
988 wxString condition;
989
990 if( rule->m_Condition )
991 condition = rule->m_Condition->GetExpression();
992
993 condition.Trim( true ).Trim( false );
994
995 auto key = std::make_pair( condition, rule->m_LayerSource );
996
997 if( seenConditions.count( key ) )
998 {
999 m_errorsReport->Report( wxString::Format( _( "Rules '%s' and '%s' share the same condition." ),
1000 rule->m_Name,
1001 seenConditions[key] ),
1003 }
1004 else
1005 {
1006 seenConditions[key] = rule->m_Name;
1007 }
1008
1009 std::string condUtf8 = condition.ToStdString();
1010 std::sregex_iterator it( condUtf8.begin(), condUtf8.end(), netclassPattern );
1011 std::sregex_iterator end;
1012
1013 for( ; it != end; ++it )
1014 {
1015 wxString ncName = wxString::FromUTF8( ( *it )[1].str() );
1016
1017 if( !board->GetDesignSettings().m_NetSettings->HasNetclass( ncName ) )
1018 {
1019 m_errorsReport->Report( wxString::Format( _( "Rule '%s' references undefined netclass '%s'." ),
1020 rule->m_Name,
1021 ncName ),
1023 }
1024 }
1025
1026 const bool isInner = rule->m_LayerSource.IsSameAs( wxT( "'inner'" ), false );
1027 const bool isOuter = rule->m_LayerSource.IsSameAs( wxT( "'outer'" ), false );
1028
1029 if( !rule->m_LayerSource.IsEmpty() && !isInner && !isOuter )
1030 {
1031 LSET invalid = rule->m_LayerCondition & ~enabledLayers;
1032
1033 if( invalid.any() )
1034 {
1035 wxString badLayers;
1036
1037 for( PCB_LAYER_ID layer : invalid.Seq() )
1038 {
1039 if( !badLayers.IsEmpty() )
1040 badLayers += ", ";
1041
1042 badLayers += board->GetLayerName( layer );
1043 }
1044
1045 m_errorsReport->Report( wxString::Format( _( "Rule '%s' references undefined layer(s): %s." ),
1046 rule->m_Name,
1047 badLayers ),
1049 }
1050 }
1051 }
1052}
1053
1054
1055void PANEL_SETUP_RULES::OnErrorLinkClicked( wxHtmlLinkEvent& event )
1056{
1057 wxString link = event.GetLinkInfo().GetHref();
1058 wxArrayString parts;
1059 long line = 0, offset = 0;
1060
1061 wxStringSplit( link, parts, ':' );
1062
1063 if( parts.size() > 1 )
1064 {
1065 parts[0].ToLong( &line );
1066 parts[1].ToLong( &offset );
1067 }
1068
1069 int pos = m_textEditor->PositionFromLine( line - 1 ) + ( offset - 1 );
1070
1071 m_textEditor->GotoPos( pos );
1072
1073 m_textEditor->SetFocus();
1074}
1075
1076
1078{
1079 if( !m_frame->GetBoard() )
1080 return true;
1081
1082 wxFileName rulesFile( m_frame->GetBoard()->GetDesignRulesPath() );
1083
1084 if( rulesFile.FileExists() )
1085 {
1086 wxTextFile file( rulesFile.GetFullPath() );
1087
1088 if( file.Open() )
1089 {
1090 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
1091 {
1093 m_textEditor->AddText( str << '\n' );
1094 }
1095
1096 m_textEditor->EmptyUndoBuffer();
1097
1098 wxCommandEvent dummy;
1099 OnCompile( dummy );
1100 }
1101 }
1102 else
1103 {
1104 m_textEditor->AddText( wxT( "(version 2)\n" ) );
1105 }
1106
1107 m_originalText = m_textEditor->GetText();
1108
1109 if( m_frame->Prj().IsNullProject() )
1110 {
1111 m_textEditor->ClearAll();
1112 m_textEditor->AddText( _( "Design rules cannot be added without a project" ) );
1113 m_textEditor->Disable();
1114 }
1115
1116 return true;
1117}
1118
1119
1121{
1122 if( !IsModified() )
1123 return true;
1124
1125 wxString rulesFilepath = m_frame->GetBoard()->GetDesignRulesPath();
1126
1127 wxString content = m_textEditor->GetText();
1128 std::string utf8 = std::string( content.mb_str( wxConvUTF8 ) );
1129 wxString writeError;
1130
1131 if( !KIPLATFORM::IO::AtomicWriteFile( rulesFilepath, utf8.data(), utf8.size(), &writeError ) )
1132 {
1133 wxLogError( _( "Cannot save design rules to '%s': %s" ), rulesFilepath, writeError );
1134 return false;
1135 }
1136
1137 try
1138 {
1139 m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine( rulesFilepath );
1140 return true;
1141 }
1142 catch( PARSE_ERROR& )
1143 {
1144 // Don't lock them in to the Setup dialog if they have bad rules. They've already
1145 // saved them so we can allow an exit.
1146 return true;
1147 }
1148}
1149
1150
1151void PANEL_SETUP_RULES::OnSyntaxHelp( wxHyperlinkEvent& aEvent )
1152{
1153 if( m_helpWindow )
1154 {
1155 m_helpWindow->ShowModeless();
1156 return;
1157 }
1158 std::vector<wxString> msg;
1159 msg.clear();
1160
1161 wxString t =
1163 ;
1164 msg.emplace_back( t );
1165 t =
1167 ;
1168 msg.emplace_back( t );
1169 t =
1171 ;
1172 msg.emplace_back( t );
1173 t =
1175 ;
1176 msg.emplace_back( t );
1177 t =
1179 ;
1180 msg.emplace_back( t );
1181 t =
1183 ;
1184 msg.emplace_back( t );
1185 t =
1187 ;
1188 msg.emplace_back( t );
1189 t =
1191 ;
1192 msg.emplace_back( t );
1193 t =
1195 ;
1196 msg.emplace_back( t );
1197 t =
1199 ;
1200 msg.emplace_back( t );
1201
1202 wxString msg_txt = wxEmptyString;
1203
1204 for( wxString i : msg )
1205 msg_txt << wxGetTranslation( i );
1206
1207#ifdef __WXMAC__
1208 msg_txt.Replace( wxT( "Ctrl+" ), wxT( "Cmd+" ) );
1209#endif
1210 const wxString& msGg_txt = msg_txt;
1211
1212 m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Syntax Help" ) );
1213 m_helpWindow->SetDialogSizeInDU( 420, 320 );
1214
1215 wxString html_txt = wxEmptyString;
1216 ConvertMarkdown2Html( msGg_txt, html_txt );
1217
1218 html_txt.Replace( wxS( "<td" ), wxS( "<td valign=top" ) );
1219 m_helpWindow->AddHTML_Text( html_txt );
1220
1221 m_helpWindow->ShowModeless();
1222}
1223
1224
1226{
1227 if( !m_frame->Prj().IsNullProject() )
1228 {
1229 wxFileName relFile = aBoard->GetFileName();
1230 relFile.SetExt( FILEEXT::DesignRulesFileExtension );
1231
1232 wxFileName absFile( aBoard->GetProject()->AbsolutePath( relFile.GetFullName() ) );
1233
1234 if( absFile.FileExists() )
1235 {
1236 wxTextFile file( absFile.GetFullPath() );
1237
1238 if( file.Open() )
1239 {
1240 m_textEditor->ClearAll();
1241
1242 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
1243 {
1245 m_textEditor->AddText( str << '\n' );
1246 }
1247
1248 m_textEditor->EmptyUndoBuffer();
1249
1250 wxCommandEvent dummy;
1251 OnCompile( dummy );
1252 }
1253 }
1254 }
1255}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
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:85
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
std::set< wxString > GetNetClassAssignmentCandidates() const
Return the set of netname candidates for netclass assignment.
Definition board.cpp:2935
const wxString & GetFileName() const
Definition board.h:454
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:932
PROJECT * GetProject() const
Definition board.h:774
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1174
BOARD_DESIGN_SETTINGS & GetDesignSettings()
Definition board.cpp:1290
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:339
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:500
Provide class metadata.
const std::vector< PROPERTY_BASE * > & GetProperties(TYPE_ID aType) const
Return all properties for a specific type.
static PROPERTY_MANAGER & Instance()
std::vector< CLASS_INFO > GetAllClasses()
Add cut/copy/paste, dark theme, autocomplete and brace highlighting to a wxStyleTextCtrl instance.
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
@ RESOLVED
Definition common.h:97
bool IsComponentClassSelector(const wxString &aToken)
${Class:X} inside a DRC rule is a component-class selector consumed by testFootprintSelector(),...
Definition drc_rule.cpp:32
@ DSN_LEFT
Definition dsnlexer.h:63
@ DSN_RIGHT
Definition dsnlexer.h:62
@ DSN_EOF
Definition dsnlexer.h:65
#define _(s)
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:83
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:103
@ PCB_NETINFO_T
class NETINFO_ITEM, a description of a net
Definition typeinfo.h:102
Definition of file extensions used in Kicad.