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>
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 if( aEvent.GetKeyCode() == WXK_ESCAPE && !m_textEditor->AutoCompActive() )
111 {
112 if( m_originalText != m_textEditor->GetText() )
113 {
114 if( IsOK( wxGetTopLevelParent( this ), _( "Cancel Changes?" ) ) )
115 {
116 m_textEditor->SetText( m_originalText );
117 m_textEditor->SelectAll();
118 }
119
120 return;
121 }
122 }
123
124 aEvent.Skip();
125}
126
127
128void PANEL_SETUP_RULES::OnContextMenu(wxMouseEvent &event)
129{
130 wxMenu menu;
131
132 menu.Append( wxID_UNDO, _( "Undo" ) );
133 menu.Append( wxID_REDO, _( "Redo" ) );
134
135 menu.AppendSeparator();
136
137 menu.Append( 1, _( "Cut" ) ); // Don't use wxID_CUT, wxID_COPY, etc. On Mac (at least),
138 menu.Append( 2, _( "Copy" ) ); // wxWidgets never delivers them to us.
139 menu.Append( 3, _( "Paste" ) );
140 menu.Append( 4, _( "Delete" ) );
141
142 menu.AppendSeparator();
143
144 menu.Append( 5, _( "Select All" ) );
145
146 menu.AppendSeparator();
147
148 menu.Append( wxID_ZOOM_IN, _( "Zoom In" ) );
149 menu.Append( wxID_ZOOM_OUT, _( "Zoom Out" ) );
150
151 menu.AppendSeparator();
152
153 menu.Append( 6, _( "Show Matching Items" ) );
154
155 switch( GetPopupMenuSelectionFromUser( menu ) )
156 {
157 case wxID_UNDO:
158 m_textEditor->Undo();
159 break;
160 case wxID_REDO:
161 m_textEditor->Redo();
162 break;
163
164 case 1:
165 m_textEditor->Cut();
166 break;
167 case 2:
168 m_textEditor->Copy();
169 break;
170 case 3:
171 m_textEditor->Paste();
172 break;
173 case 4:
174 {
175 long from, to;
176 m_textEditor->GetSelection( &from, &to );
177
178 if( to > from )
179 m_textEditor->DeleteRange( from, to );
180
181 break;
182 }
183
184 case 5:
185 m_textEditor->SelectAll();
186 break;
187
188 case wxID_ZOOM_IN:
189 m_textEditor->ZoomIn();
190 break;
191 case wxID_ZOOM_OUT:
192 m_textEditor->ZoomOut();
193 break;
194
195 case 6: OnShowMatching(); break;
196 }
197}
198
199
201{
202 m_errorsReport->Clear();
203
204 if( m_frame->Prj().IsNullProject() )
205 {
206 m_errorsReport->Report( _( "Cannot show matching items without an open project." ), RPT_SEVERITY_ERROR );
207 m_errorsReport->Flush();
208 return;
209 }
210
211 const wxString text = m_textEditor->GetText();
212 const int cursorPos = m_textEditor->GetCurrentPos();
213 const int cursorLine = m_textEditor->LineFromPosition( cursorPos ) + 1;
214 const int cursorCol = cursorPos - m_textEditor->PositionFromLine( cursorLine - 1 ) + 1;
215
216 std::vector<std::shared_ptr<DRC_RULE>> rules;
217
218 try
219 {
220 std::function<bool( wxString* )> resolver = [&]( wxString* token ) -> bool
221 {
222 return m_frame->GetBoard()->ResolveTextVar( token, 0 );
223 };
224
225 wxString rulesText = m_frame->GetBoard()->ConvertCrossReferencesToKIIDs( text );
226 rulesText = ExpandTextVars( rulesText, &resolver );
227
228 DRC_RULES_PARSER parser( rulesText, _( "DRC rules" ) );
229 parser.Parse( rules, m_errorsReport );
230 }
231 catch( PARSE_ERROR& pe )
232 {
233 m_errorsReport->Report( wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>%s" ), _( "ERROR:" ), pe.lineNumber,
234 pe.byteIndex, pe.ParseProblem(), wxEmptyString ),
236 m_errorsReport->Flush();
237 return;
238 }
239
240 wxString targetRuleName;
241 bool foundRule = false;
242
243 try
244 {
245 std::string utf8( text.mb_str( wxConvUTF8 ) );
246 DRC_RULES_LEXER lex( utf8, _( "DRC rules" ) );
247
248 struct RuleFrame
249 {
250 int depth;
251 int startLine;
252 int startCol;
253 wxString name;
254 };
255
256 std::vector<RuleFrame> ruleStack;
257 int depth = 0;
258
259 for( ;; )
260 {
261 int tok = lex.NextTok();
262
263 if( tok == DSN_EOF )
264 break;
265
266 if( tok == DSN_LEFT )
267 {
268 int leftLine = lex.CurLineNumber();
269 int leftCol = lex.CurOffset();
270
271 depth++;
272
273 int sub = lex.NextTok();
274
275 if( sub == DRCRULE_T::T_rule )
276 {
277 wxString name;
278 int nameTok = lex.NextTok();
279
280 if( DSNLEXER::IsSymbol( nameTok ) )
281 name = wxString::FromUTF8( lex.CurText() );
282
283 ruleStack.push_back( { depth, leftLine, leftCol, name } );
284 }
285 else if( sub == DSN_LEFT )
286 {
287 depth++;
288 }
289 else if( sub == DSN_RIGHT )
290 {
291 depth--;
292 }
293 else if( sub == DSN_EOF )
294 {
295 break;
296 }
297 }
298 else if( tok == DSN_RIGHT )
299 {
300 if( !ruleStack.empty() && ruleStack.back().depth == depth )
301 {
302 RuleFrame opened = ruleStack.back();
303 ruleStack.pop_back();
304
305 int endLine = lex.CurLineNumber();
306 int endCol = lex.CurOffset();
307
308 bool afterStart = cursorLine > opened.startLine
309 || ( cursorLine == opened.startLine && cursorCol >= opened.startCol );
310 bool beforeEnd = cursorLine < endLine || ( cursorLine == endLine && cursorCol <= endCol );
311
312 if( afterStart && beforeEnd )
313 {
314 targetRuleName = opened.name;
315 foundRule = true;
316 break;
317 }
318 }
319
320 depth--;
321 }
322 }
323 }
324 catch( IO_ERROR& e )
325 {
326 m_errorsReport->Report( wxString::Format( _( "Could not scan rules text: %s" ), e.What() ),
328 m_errorsReport->Flush();
329 return;
330 }
331
332 if( !foundRule )
333 {
334 m_errorsReport->Report( _( "Place the cursor inside a (rule ...) block to show matching "
335 "items." ),
337 m_errorsReport->Flush();
338 return;
339 }
340
341 if( targetRuleName.IsEmpty() )
342 {
343 m_errorsReport->Report( _( "The rule under the cursor has no name; add a name to identify "
344 "it." ),
346 m_errorsReport->Flush();
347 return;
348 }
349
350 std::shared_ptr<DRC_RULE> targetRule;
351
352 for( const auto& rule : rules )
353 {
354 if( rule->m_Name == targetRuleName )
355 {
356 targetRule = rule;
357 break;
358 }
359 }
360
361 if( !targetRule )
362 {
363 m_errorsReport->Report( wxString::Format( _( "Rule '%s' could not be located after "
364 "parsing." ),
365 targetRuleName ),
367 m_errorsReport->Flush();
368 return;
369 }
370
371 if( targetRule->m_Condition && !targetRule->m_Condition->GetExpression().IsEmpty()
372 && !targetRule->m_Condition->Compile( m_errorsReport ) )
373 {
374 m_errorsReport->Flush();
375 return;
376 }
377
378 std::shared_ptr<DRC_ENGINE> engine = m_frame->GetBoard()->GetDesignSettings().m_DRCEngine;
379
380 if( !engine )
381 {
382 m_errorsReport->Report( _( "DRC engine not available." ), RPT_SEVERITY_ERROR );
383 m_errorsReport->Flush();
384 return;
385 }
386
387 std::vector<BOARD_ITEM*> allMatches = engine->GetItemsMatchingRule( targetRule, m_errorsReport );
388
389 std::vector<BOARD_ITEM*> matches;
390
391 for( BOARD_ITEM* item : allMatches )
392 {
393 switch( item->Type() )
394 {
395 case PCB_NETINFO_T:
396 case PCB_GENERATOR_T:
397 case PCB_GROUP_T: continue;
398 default: matches.push_back( item ); break;
399 }
400 }
401
402 m_frame->FocusOnItems( matches );
403
404 m_errorsReport->Report(
405 wxString::Format( _( "Rule '%s': %zu matching item(s)." ), targetRule->m_Name, matches.size() ),
407 m_errorsReport->Flush();
408}
409
410
411void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
412{
413 if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKey() == ' ' )
414 {
415 // This is just a short-cut for do-auto-complete
416 }
417 else
418 {
420 }
421
422 m_textEditor->SearchAnchor();
423
424 int currentPos = m_textEditor->GetCurrentPos();
425 int startPos = 0;
426
427 for( int line = m_textEditor->LineFromPosition( currentPos ); line > 0; line-- )
428 {
429 int lineStart = m_textEditor->PositionFromLine( line );
430 wxString beginning = m_textEditor->GetTextRange( lineStart, lineStart + 10 );
431
432 if( beginning.StartsWith( wxT( "(rule " ) ) )
433 {
434 startPos = lineStart;
435 break;
436 }
437 }
438
439 enum
440 {
441 NO_CONTEXT,
442 STRING,
443 SEXPR_OPEN,
444 SEXPR_TOKEN,
445 SEXPR_STRING,
446 STRUCT_REF
447 };
448
449 auto isDisallowToken =
450 []( const wxString& token ) -> bool
451 {
452 return token == wxT( "blind_via" )
453 || token == wxT( "buried_via" )
454 || token == wxT( "graphic" )
455 || token == wxT( "hole" )
456 || token == wxT( "micro_via" )
457 || token == wxT( "pad" )
458 || token == wxT( "text" )
459 || token == wxT( "through_via" )
460 || token == wxT( "track" )
461 || token == wxT( "via" )
462 || token == wxT( "zone" );
463 };
464
465 auto isConstraintTypeToken =
466 []( const wxString& token ) -> bool
467 {
468 return token == wxT( "annular_width" )
469 || token == wxT( "assertion" )
470 || token == wxT( "clearance" )
471 || token == wxT( "connection_width" )
472 || token == wxT( "courtyard_clearance" )
473 || token == wxT( "diff_pair_gap" )
474 || token == wxT( "diff_pair_uncoupled" )
475 || token == wxT( "disallow" )
476 || token == wxT( "edge_clearance" )
477 || token == wxT( "length" )
478 || token == wxT( "hole_clearance" )
479 || token == wxT( "hole_size" )
480 || token == wxT( "hole_to_hole" )
481 || token == wxT( "min_resolved_spokes" )
482 || token == wxT( "physical_clearance" )
483 || token == wxT( "physical_hole_clearance" )
484 || token == wxT( "silk_clearance" )
485 || token == wxT( "skew" )
486 || token == wxT( "solder_mask_expansion" )
487 || token == wxT( "solder_paste_abs_margin" )
488 || token == wxT( "solder_paste_rel_margin" )
489 || token == wxT( "text_height" )
490 || token == wxT( "text_thickness" )
491 || token == wxT( "thermal_relief_gap" )
492 || token == wxT( "thermal_spoke_width" )
493 || token == wxT( "track_width" )
494 || token == wxT( "track_angle" )
495 || token == wxT( "track_segment_length" )
496 || token == wxT( "via_count" )
497 || token == wxT( "via_diameter" )
498 || token == wxT( "zone_connection" );
499 };
500
501 std::stack<wxString> sexprs;
502 wxString partial;
503 wxString last;
504 wxString constraintType;
505 int context = NO_CONTEXT;
506 int expr_context = NO_CONTEXT;
507
508 for( int i = startPos; i < currentPos; ++i )
509 {
510 wxChar c = m_textEditor->GetCharAt( i );
511
512 if( c == '\\' )
513 {
514 i++; // skip escaped char
515 }
516 else if( context == STRING )
517 {
518 if( c == '"' )
519 {
520 context = NO_CONTEXT;
521 }
522 else
523 {
524 if( expr_context == STRING )
525 {
526 if( c == '\'' )
527 expr_context = NO_CONTEXT;
528 else
529 partial += c;
530 }
531 else if( c == '\'' )
532 {
533 last = partial;
534 partial = wxEmptyString;
535 expr_context = STRING;
536 }
537 else if( c == '.' )
538 {
539 partial = wxEmptyString;
540 expr_context = STRUCT_REF;
541 }
542 else
543 {
544 partial += c;
545 }
546 }
547 }
548 else if( c == '"' )
549 {
550 last = partial;
551 partial = wxEmptyString;
552 context = STRING;
553 }
554 else if( c == '(' )
555 {
556 if( context == SEXPR_OPEN && !partial.IsEmpty() )
557 {
558 m_textEditor->AutoCompCancel();
559 sexprs.push( partial );
560 }
561
562 partial = wxEmptyString;
563 context = SEXPR_OPEN;
564 }
565 else if( c == ')' )
566 {
567 while( !sexprs.empty() && ( sexprs.top() == wxT( "assertion" )
568 || sexprs.top() == wxT( "disallow" )
569 || isDisallowToken( sexprs.top() )
570 || sexprs.top() == wxT( "min_resolved_spokes" )
571 || sexprs.top() == wxT( "zone_connection" ) ) )
572 {
573 sexprs.pop();
574 }
575
576 if( !sexprs.empty() )
577 {
578 // Ignore argument-less tokens
579 if( partial == wxT( "within_diff_pairs" ) )
580 {
581 partial = wxEmptyString;
582 }
583 else
584 {
585 if( sexprs.top() == wxT( "constraint" ) )
586 {
587 constraintType = wxEmptyString;
588 }
589
590 sexprs.pop();
591 }
592 }
593
594 context = NO_CONTEXT;
595 }
596 else if( c == ' ' )
597 {
598 if( context == SEXPR_OPEN && !partial.IsEmpty() )
599 {
600 m_textEditor->AutoCompCancel();
601 sexprs.push( partial );
602
603 if( partial == wxT( "constraint" )
604 || partial == wxT( "layer" )
605 || partial == wxT( "severity" ) )
606 {
607 context = SEXPR_TOKEN;
608 }
609 else if( partial == wxT( "rule" )
610 || partial == wxT( "condition" ) )
611 {
612 context = SEXPR_STRING;
613 }
614 else
615 {
616 context = NO_CONTEXT;
617 }
618
619 partial = wxEmptyString;
620 continue;
621 }
622 else if( partial == wxT( "disallow" )
623 || isDisallowToken( partial )
624 || partial == wxT( "min_resolved_spokes" )
625 || partial == wxT( "zone_connection" ) )
626 {
627 m_textEditor->AutoCompCancel();
628 sexprs.push( partial );
629
630 partial = wxEmptyString;
631 context = SEXPR_TOKEN;
632 continue;
633 }
634 else if( partial == wxT( "assertion" ) )
635 {
636 m_textEditor->AutoCompCancel();
637 sexprs.push( partial );
638
639 partial = wxEmptyString;
640 context = SEXPR_STRING;
641 continue;
642 }
643 else if( isConstraintTypeToken( partial ) )
644 {
645 constraintType = partial;
646 }
647
648 context = NO_CONTEXT;
649 }
650 else
651 {
652 partial += c;
653 }
654 }
655
656 wxString tokens;
657
658 if( context == SEXPR_OPEN )
659 {
660 if( sexprs.empty() )
661 {
662 tokens = wxT( "rule|"
663 "version" );
664 }
665 else if( sexprs.top() == wxT( "rule" ) )
666 {
667 tokens = wxT( "condition|"
668 "constraint|"
669 "layer|"
670 "severity" );
671 }
672 else if( sexprs.top() == wxT( "constraint" ) )
673 {
674 if( constraintType == wxT( "skew" ) )
675 tokens = wxT( "max|min|opt|within_diff_pairs" );
676 else
677 tokens = wxT( "max|min|opt" );
678 }
679 }
680 else if( context == SEXPR_TOKEN )
681 {
682 if( sexprs.empty() )
683 {
684 /* badly formed grammar */
685 }
686 else if( sexprs.top() == wxT( "constraint" ) )
687 {
688 tokens = wxT( "annular_width|"
689 "assertion|"
690 "bridged_mask|"
691 "clearance|"
692 "connection_width|"
693 "courtyard_clearance|"
694 "creepage|"
695 "diff_pair_gap|"
696 "diff_pair_uncoupled|"
697 "disallow|"
698 "edge_clearance|"
699 "length|"
700 "hole_clearance|"
701 "hole_size|"
702 "hole_to_hole|"
703 "min_resolved_spokes|"
704 "physical_clearance|"
705 "physical_hole_clearance|"
706 "silk_clearance|"
707 "skew|"
708 "solder_mask_expansion|"
709 "solder_paste_abs_margin|"
710 "solder_paste_rel_margin|"
711 "text_height|"
712 "text_thickness|"
713 "thermal_relief_gap|"
714 "thermal_spoke_width|"
715 "track_width|"
716 "track_angle|"
717 "track_segment_length|"
718 "via_count|"
719 "via_diameter|"
720 "zone_connection" );
721 }
722 else if( sexprs.top() == wxT( "disallow" ) || isDisallowToken( sexprs.top() ) )
723 {
724 tokens = wxT( "blind_via|"
725 "buried_via|"
726 "footprint|"
727 "graphic|"
728 "hole|"
729 "micro_via|"
730 "pad|"
731 "text|"
732 "through_via|"
733 "track|"
734 "via|"
735 "zone" );
736 }
737 else if( sexprs.top() == wxT( "zone_connection" ) )
738 {
739 tokens = wxT( "none|solid|thermal_reliefs" );
740 }
741 else if( sexprs.top() == wxT( "min_resolved_spokes" ) )
742 {
743 tokens = wxT( "0|1|2|3|4" );
744 }
745 else if( sexprs.top() == wxT( "layer" ) )
746 {
747 tokens = wxT( "inner|outer|\"x\"" );
748 }
749 else if( sexprs.top() == wxT( "severity" ) )
750 {
751 tokens = wxT( "warning|error|ignore|exclusion" );
752 }
753 }
754 else if( context == SEXPR_STRING && !sexprs.empty()
755 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
756 {
757 m_textEditor->AddText( wxT( "\"" ) );
758 }
759 else if( context == STRING && !sexprs.empty()
760 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
761 {
762 if( expr_context == STRUCT_REF )
763 {
765 std::set<wxString> propNames;
766
767 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
768 {
769 const std::vector<PROPERTY_BASE*>& props = propMgr.GetProperties( cls.type );
770
771 for( PROPERTY_BASE* prop : props )
772 {
773 // TODO: It would be nice to replace IsHiddenFromRulesEditor with a nickname
774 // system, so that two different properies don't need to be created. This is
775 // a bigger change than I want to make right now, though.
776 if( prop->IsHiddenFromRulesEditor() )
777 continue;
778
779 wxString ref( prop->Name() );
780 ref.Replace( wxT( " " ), wxT( "_" ) );
781 propNames.insert( ref );
782 }
783 }
784
785 for( const wxString& propName : propNames )
786 tokens += wxT( "|" ) + propName;
787
789
790 for( const wxString& funcSig : functions.GetSignatures() )
791 {
792 if( !funcSig.Contains( "DEPRECATED" ) )
793 tokens += wxT( "|" ) + funcSig;
794 }
795 }
796 else if( expr_context == STRING )
797 {
798 if( m_netClassRegex.Matches( last ) )
799 {
800 BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
801 std::shared_ptr<NET_SETTINGS>& netSettings = bds.m_NetSettings;
802
803 for( const auto& [name, netclass] : netSettings->GetNetclasses() )
804 tokens += wxT( "|" ) + name;
805 }
806 else if( m_netNameRegex.Matches( last ) )
807 {
808 BOARD* board = m_frame->GetBoard();
809
810 for( const wxString& netnameCandidate : board->GetNetClassAssignmentCandidates() )
811 tokens += wxT( "|" ) + netnameCandidate;
812 }
813 else if( m_typeRegex.Matches( last ) )
814 {
815 tokens = wxT( "Bitmap|"
816 "Dimension|"
817 "Footprint|"
818 "Graphic|"
819 "Group|"
820 "Leader|"
821 "Pad|"
822 "Target|"
823 "Text|"
824 "Text Box|"
825 "Track|"
826 "Via|"
827 "Zone" );
828 }
829 else if( m_viaTypeRegex.Matches( last ) )
830 {
831 tokens = wxT( "Through|"
832 "Blind|"
833 "Buried|"
834 "Micro" );
835 }
836 else if( m_padTypeRegex.Matches( last ) )
837 {
838 tokens = wxT( "Through-hole|"
839 "SMD|"
840 "Edge connector|"
841 "NPTH, mechanical" );
842 }
843 else if( m_pinTypeRegex.Matches( last ) )
844 {
845 tokens = wxT( "Input|"
846 "Output|"
847 "Bidirectional|"
848 "Tri-state|"
849 "Passive|"
850 "Free|"
851 "Unspecified|"
852 "Power input|"
853 "Power output|"
854 "Open collector|"
855 "Open emitter|"
856 "Unconnected" );
857 }
858 else if( m_fabPropRegex.Matches( last ) )
859 {
860 tokens = wxT( "None|"
861 "BGA pad|"
862 "Fiducial, global to board|"
863 "Fiducial, local to footprint|"
864 "Test point pad|"
865 "Heatsink pad|"
866 "Castellated pad" );
867 }
868 else if( m_shapeRegex.Matches( last ) )
869 {
870 tokens = wxT( "Segment|"
871 "Rectangle|"
872 "Arc|"
873 "Circle|"
874 "Polygon|"
875 "Bezier" );
876 }
877 else if( m_padShapeRegex.Matches( last ) )
878 {
879 tokens = wxT( "Circle|"
880 "Rectangle|"
881 "Oval|"
882 "Trapezoid|"
883 "Rounded rectangle|"
884 "Chamfered rectangle|"
885 "Custom" );
886 }
887 else if( m_padConnectionsRegex.Matches( last ) )
888 {
889 tokens = wxT( "Inherited|"
890 "None|"
891 "Solid|"
892 "Thermal reliefs|"
893 "Thermal reliefs for PTH" );
894 }
895 else if( m_zoneConnStyleRegex.Matches( last ) )
896 {
897 tokens = wxT( "Inherited|"
898 "None|"
899 "Solid|"
900 "Thermal reliefs" );
901 }
902 else if( m_lineStyleRegex.Matches( last ) )
903 {
904 tokens = wxT( "Default|"
905 "Solid|"
906 "Dashed|"
907 "Dotted|"
908 "Dash-Dot|"
909 "Dash-Dot-Dot" );
910 }
911 else if( m_hJustRegex.Matches( last ) )
912 {
913 tokens = wxT( "Left|"
914 "Center|"
915 "Right" );
916 }
917 else if( m_vJustRegex.Matches( last ) )
918 {
919 tokens = wxT( "Top|"
920 "Center|"
921 "Bottom" );
922 }
923 }
924 }
925
926 if( !tokens.IsEmpty() )
927 m_scintillaTricks->DoAutocomplete( partial, wxSplit( tokens, '|' ) );
928}
929
930
931void PANEL_SETUP_RULES::OnCompile( wxCommandEvent& event )
932{
933 m_errorsReport->Clear();
934
935 try
936 {
937 std::vector<std::shared_ptr<DRC_RULE>> dummyRules;
938
939 std::function<bool( wxString* )> resolver =
940 [&]( wxString* token ) -> bool
941 {
942 if( m_frame->GetBoard()->ResolveTextVar( token, 0 ) )
943 return true;
944
945 return false;
946 };
947
948 wxString rulesText = m_textEditor->GetText();
949 rulesText = m_frame->GetBoard()->ConvertCrossReferencesToKIIDs( rulesText );
950 rulesText = ExpandTextVars( rulesText, &resolver );
951
952 DRC_RULES_PARSER parser( rulesText, _( "DRC rules" ) );
953
954 parser.Parse( dummyRules, m_errorsReport );
955 checkPlausibility( dummyRules );
956 }
957 catch( PARSE_ERROR& pe )
958 {
959 m_errorsReport->Report( wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>%s" ),
960 _( "ERROR:" ),
961 pe.lineNumber,
962 pe.byteIndex,
963 pe.ParseProblem(),
964 wxEmptyString ),
966 }
967
968 m_errorsReport->Flush();
969}
970
971
972void PANEL_SETUP_RULES::checkPlausibility( const std::vector<std::shared_ptr<DRC_RULE>>& aRules )
973{
974 BOARD* board = m_frame->GetBoard();
976 LSET enabledLayers = board->GetEnabledLayers();
977
978 // Key by (condition, layerSource) so rules with different layer scopes are considered distinct
979 std::map<std::pair<wxString, wxString>, wxString> seenConditions;
980 std::regex netclassPattern( "NetClass\\s*[!=]=\\s*'\"?([^\"\\s]+)'\"?" );
981
982 for( const auto& rule : aRules )
983 {
984 wxString condition;
985
986 if( rule->m_Condition )
987 condition = rule->m_Condition->GetExpression();
988
989 condition.Trim( true ).Trim( false );
990
991 auto key = std::make_pair( condition, rule->m_LayerSource );
992
993 if( seenConditions.count( key ) )
994 {
995 m_errorsReport->Report( wxString::Format( _( "Rules '%s' and '%s' share the same condition." ),
996 rule->m_Name,
997 seenConditions[key] ),
999 }
1000 else
1001 {
1002 seenConditions[key] = rule->m_Name;
1003 }
1004
1005 std::string condUtf8 = condition.ToStdString();
1006 std::sregex_iterator it( condUtf8.begin(), condUtf8.end(), netclassPattern );
1007 std::sregex_iterator end;
1008
1009 for( ; it != end; ++it )
1010 {
1011 wxString ncName = wxString::FromUTF8( ( *it )[1].str() );
1012
1013 if( !bds.m_NetSettings->HasNetclass( ncName ) )
1014 {
1015 m_errorsReport->Report( wxString::Format( _( "Rule '%s' references undefined netclass '%s'." ),
1016 rule->m_Name,
1017 ncName ),
1019 }
1020 }
1021
1022 const bool isInner = rule->m_LayerSource.IsSameAs( wxT( "'inner'" ), false );
1023 const bool isOuter = rule->m_LayerSource.IsSameAs( wxT( "'outer'" ), false );
1024
1025 if( !rule->m_LayerSource.IsEmpty() && !isInner && !isOuter )
1026 {
1027 LSET invalid = rule->m_LayerCondition & ~enabledLayers;
1028
1029 if( invalid.any() )
1030 {
1031 wxString badLayers;
1032
1033 for( PCB_LAYER_ID layer : invalid.Seq() )
1034 {
1035 if( !badLayers.IsEmpty() )
1036 badLayers += ", ";
1037
1038 badLayers += board->GetLayerName( layer );
1039 }
1040
1041 m_errorsReport->Report( wxString::Format( _( "Rule '%s' references undefined layer(s): %s." ),
1042 rule->m_Name,
1043 badLayers ),
1045 }
1046 }
1047 }
1048}
1049
1050
1051void PANEL_SETUP_RULES::OnErrorLinkClicked( wxHtmlLinkEvent& event )
1052{
1053 wxString link = event.GetLinkInfo().GetHref();
1054 wxArrayString parts;
1055 long line = 0, offset = 0;
1056
1057 wxStringSplit( link, parts, ':' );
1058
1059 if( parts.size() > 1 )
1060 {
1061 parts[0].ToLong( &line );
1062 parts[1].ToLong( &offset );
1063 }
1064
1065 int pos = m_textEditor->PositionFromLine( line - 1 ) + ( offset - 1 );
1066
1067 m_textEditor->GotoPos( pos );
1068
1069 m_textEditor->SetFocus();
1070}
1071
1072
1074{
1075 if( !m_frame->GetBoard() )
1076 return true;
1077
1078 wxFileName rulesFile( m_frame->GetBoard()->GetDesignRulesPath() );
1079
1080 if( rulesFile.FileExists() )
1081 {
1082 wxTextFile file( rulesFile.GetFullPath() );
1083
1084 if( file.Open() )
1085 {
1086 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
1087 {
1089 m_textEditor->AddText( str << '\n' );
1090 }
1091
1092 m_textEditor->EmptyUndoBuffer();
1093
1094 wxCommandEvent dummy;
1095 OnCompile( dummy );
1096 }
1097 }
1098 else
1099 {
1100 m_textEditor->AddText( wxT( "(version 2)\n" ) );
1101 }
1102
1103 m_originalText = m_textEditor->GetText();
1104
1105 if( m_frame->Prj().IsNullProject() )
1106 {
1107 m_textEditor->ClearAll();
1108 m_textEditor->AddText( _( "Design rules cannot be added without a project" ) );
1109 m_textEditor->Disable();
1110 }
1111
1112 return true;
1113}
1114
1115
1117{
1118 if( m_originalText == m_textEditor->GetText() )
1119 return true;
1120
1121 if( m_frame->Prj().IsNullProject() )
1122 return true;
1123
1124 if( !m_frame->GetBoard() )
1125 return true;
1126
1127 wxString rulesFilepath = m_frame->GetBoard()->GetDesignRulesPath();
1128
1129 wxString content = m_textEditor->GetText();
1130 std::string utf8 = std::string( content.mb_str( wxConvUTF8 ) );
1131 wxString writeError;
1132
1133 if( !KIPLATFORM::IO::AtomicWriteFile( rulesFilepath, utf8.data(), utf8.size(), &writeError ) )
1134 {
1135 wxLogError( _( "Cannot save design rules to '%s': %s" ), rulesFilepath, writeError );
1136 return false;
1137 }
1138
1139 try
1140 {
1141 m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine( rulesFilepath );
1142 return true;
1143 }
1144 catch( PARSE_ERROR& )
1145 {
1146 // Don't lock them in to the Setup dialog if they have bad rules. They've already
1147 // saved them so we can allow an exit.
1148 return true;
1149 }
1150}
1151
1152
1153void PANEL_SETUP_RULES::OnSyntaxHelp( wxHyperlinkEvent& aEvent )
1154{
1155 if( m_helpWindow )
1156 {
1157 m_helpWindow->ShowModeless();
1158 return;
1159 }
1160 std::vector<wxString> msg;
1161 msg.clear();
1162
1163 wxString t =
1165 ;
1166 msg.emplace_back( t );
1167 t =
1169 ;
1170 msg.emplace_back( t );
1171 t =
1173 ;
1174 msg.emplace_back( t );
1175 t =
1177 ;
1178 msg.emplace_back( t );
1179 t =
1181 ;
1182 msg.emplace_back( t );
1183 t =
1185 ;
1186 msg.emplace_back( t );
1187 t =
1189 ;
1190 msg.emplace_back( t );
1191 t =
1193 ;
1194 msg.emplace_back( t );
1195 t =
1197 ;
1198 msg.emplace_back( t );
1199 t =
1201 ;
1202 msg.emplace_back( t );
1203
1204 wxString msg_txt = wxEmptyString;
1205
1206 for( wxString i : msg )
1207 msg_txt << wxGetTranslation( i );
1208
1209#ifdef __WXMAC__
1210 msg_txt.Replace( wxT( "Ctrl+" ), wxT( "Cmd+" ) );
1211#endif
1212 const wxString& msGg_txt = msg_txt;
1213
1214 m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Syntax Help" ) );
1215 m_helpWindow->SetDialogSizeInDU( 420, 320 );
1216
1217 wxString html_txt = wxEmptyString;
1218 ConvertMarkdown2Html( msGg_txt, html_txt );
1219
1220 html_txt.Replace( wxS( "<td" ), wxS( "<td valign=top" ) );
1221 m_helpWindow->AddHTML_Text( html_txt );
1222
1223 m_helpWindow->ShowModeless();
1224}
1225
1226
1228{
1229 if( !m_frame->Prj().IsNullProject() )
1230 {
1231 wxFileName relFile = aBoard->GetFileName();
1232 relFile.SetExt( FILEEXT::DesignRulesFileExtension );
1233
1234 wxFileName absFile( aBoard->GetProject()->AbsolutePath( relFile.GetFullName() ) );
1235
1236 if( absFile.FileExists() )
1237 {
1238 wxTextFile file( absFile.GetFullPath() );
1239
1240 if( file.Open() )
1241 {
1242 m_textEditor->ClearAll();
1243
1244 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
1245 {
1247 m_textEditor->AddText( str << '\n' );
1248 }
1249
1250 m_textEditor->EmptyUndoBuffer();
1251
1252 wxCommandEvent dummy;
1253 OnCompile( dummy );
1254 }
1255 }
1256 }
1257}
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:81
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
std::set< wxString > GetNetClassAssignmentCandidates() const
Return the set of netname candidates for netclass assignment.
Definition board.cpp:2795
const wxString & GetFileName() const
Definition board.h:409
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:793
PROJECT * GetProject() const
Definition board.h:650
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1149
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1034
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:528
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.
@ 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.