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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include <bitmaps.h>
25#include <common.h>
26#include <confirm.h>
27#include <kiplatform/io.h>
30#include <pcb_edit_frame.h>
31#include <pcbexpr_evaluator.h>
32#include <board.h>
34#include <drc/drc_engine.h>
37#include <project.h>
38#include <string_utils.h>
39#include <tool/tool_manager.h>
40#include <panel_setup_rules.h>
43#include <scintilla_tricks.h>
45#include <drc/drc_rule_parser.h>
46#include <tools/drc_tool.h>
47#include <pgm_base.h>
49#include <regex>
50#include <unordered_map>
51#include <properties/property.h>
53
54PANEL_SETUP_RULES::PANEL_SETUP_RULES( wxWindow* aParentWindow, PCB_EDIT_FRAME* aFrame ) :
55 PANEL_SETUP_RULES_BASE( aParentWindow ),
56 m_frame( aFrame ),
57 m_scintillaTricks( nullptr ),
58 m_helpWindow( nullptr )
59{
60 m_scintillaTricks = new SCINTILLA_TRICKS( m_textEditor, wxT( "()" ), false,
61 // onAcceptFn
62 [this]( wxKeyEvent& aEvent )
63 {
64 wxPostEvent( PAGED_DIALOG::GetDialog( this ),
65 wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
66 },
67 // onCharFn
68 [this]( wxStyledTextEvent& aEvent )
69 {
70 onScintillaCharAdded( aEvent );
71 } );
72
73 m_textEditor->AutoCompSetSeparator( '|' );
74
75 m_netClassRegex.Compile( "^NetClass\\s*[!=]=\\s*$", wxRE_ADVANCED );
76 m_netNameRegex.Compile( "^NetName\\s*[!=]=\\s*$", wxRE_ADVANCED );
77 m_typeRegex.Compile( "^Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
78 m_viaTypeRegex.Compile( "^Via_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
79 m_padTypeRegex.Compile( "^Pad_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
80 m_pinTypeRegex.Compile( "^Pin_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
81 m_fabPropRegex.Compile( "^Fabrication_Property\\s*[!=]=\\s*$", wxRE_ADVANCED );
82 m_shapeRegex.Compile( "^Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
83 m_padShapeRegex.Compile( "^Pad_Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
84 m_padConnectionsRegex.Compile( "^Pad_Connections\\s*[!=]=\\s*$", wxRE_ADVANCED );
85 m_zoneConnStyleRegex.Compile( "^Zone_Connection_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
86 m_lineStyleRegex.Compile( "^Line_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
87 m_hJustRegex.Compile( "^Horizontal_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
88 m_vJustRegex.Compile( "^Vertical_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
89
91
92 m_textEditor->SetZoom( Pgm().GetCommonSettings()->m_Appearance.text_editor_zoom );
93
94 m_textEditor->UsePopUp( 0 );
95 m_textEditor->Bind( wxEVT_CHAR_HOOK, &PANEL_SETUP_RULES::onCharHook, this );
96}
97
98
100{
101 m_textEditor->Unbind( wxEVT_CHAR_HOOK, &PANEL_SETUP_RULES::onCharHook, this );
103
104 delete m_scintillaTricks;
105 m_scintillaTricks = nullptr;
106
107 if( m_helpWindow )
108 m_helpWindow->Destroy();
109};
110
111
112void PANEL_SETUP_RULES::onCharHook( wxKeyEvent& aEvent )
113{
114 if( aEvent.GetKeyCode() == WXK_ESCAPE && !m_textEditor->AutoCompActive() )
115 {
116 if( m_originalText != m_textEditor->GetText() )
117 {
118 if( IsOK( wxGetTopLevelParent( this ), _( "Cancel Changes?" ) ) )
119 {
120 m_textEditor->SetText( m_originalText );
121 m_textEditor->SelectAll();
122 }
123
124 return;
125 }
126 }
127
128 aEvent.Skip();
129}
130
131
132void PANEL_SETUP_RULES::OnContextMenu(wxMouseEvent &event)
133{
134 wxMenu menu;
135
136 menu.Append( wxID_UNDO, _( "Undo" ) );
137 menu.Append( wxID_REDO, _( "Redo" ) );
138
139 menu.AppendSeparator();
140
141 menu.Append( 1, _( "Cut" ) ); // Don't use wxID_CUT, wxID_COPY, etc. On Mac (at least),
142 menu.Append( 2, _( "Copy" ) ); // wxWidgets never delivers them to us.
143 menu.Append( 3, _( "Paste" ) );
144 menu.Append( 4, _( "Delete" ) );
145
146 menu.AppendSeparator();
147
148 menu.Append( 5, _( "Select All" ) );
149
150 menu.AppendSeparator();
151
152 menu.Append( wxID_ZOOM_IN, _( "Zoom In" ) );
153 menu.Append( wxID_ZOOM_OUT, _( "Zoom Out" ) );
154
155 menu.AppendSeparator();
156
157 menu.Append( 6, _( "Show Matching Items" ) );
158
159 switch( GetPopupMenuSelectionFromUser( menu ) )
160 {
161 case wxID_UNDO:
162 m_textEditor->Undo();
163 break;
164 case wxID_REDO:
165 m_textEditor->Redo();
166 break;
167
168 case 1:
169 m_textEditor->Cut();
170 break;
171 case 2:
172 m_textEditor->Copy();
173 break;
174 case 3:
175 m_textEditor->Paste();
176 break;
177 case 4:
178 {
179 long from, to;
180 m_textEditor->GetSelection( &from, &to );
181
182 if( to > from )
183 m_textEditor->DeleteRange( from, to );
184
185 break;
186 }
187
188 case 5:
189 m_textEditor->SelectAll();
190 break;
191
192 case wxID_ZOOM_IN:
193 m_textEditor->ZoomIn();
194 break;
195 case wxID_ZOOM_OUT:
196 m_textEditor->ZoomOut();
197 break;
198
199 case 6: OnShowMatching(); break;
200 }
201}
202
203
205{
206 m_errorsReport->Clear();
207
208 if( m_frame->Prj().IsNullProject() )
209 {
210 m_errorsReport->Report( _( "Cannot show matching items without an open project." ), RPT_SEVERITY_ERROR );
211 m_errorsReport->Flush();
212 return;
213 }
214
215 const wxString text = m_textEditor->GetText();
216 const int cursorPos = m_textEditor->GetCurrentPos();
217 const int cursorLine = m_textEditor->LineFromPosition( cursorPos ) + 1;
218 const int cursorCol = cursorPos - m_textEditor->PositionFromLine( cursorLine - 1 ) + 1;
219
220 std::vector<std::shared_ptr<DRC_RULE>> rules;
221
222 try
223 {
224 std::function<bool( wxString* )> resolver = [&]( wxString* token ) -> bool
225 {
226 return m_frame->GetBoard()->ResolveTextVar( token, 0 );
227 };
228
229 wxString rulesText = m_frame->GetBoard()->ConvertCrossReferencesToKIIDs( text );
230 rulesText = ExpandTextVars( rulesText, &resolver );
231
232 DRC_RULES_PARSER parser( rulesText, _( "DRC rules" ) );
233 parser.Parse( rules, m_errorsReport );
234 }
235 catch( PARSE_ERROR& pe )
236 {
237 m_errorsReport->Report( wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>%s" ), _( "ERROR:" ), pe.lineNumber,
238 pe.byteIndex, pe.ParseProblem(), wxEmptyString ),
240 m_errorsReport->Flush();
241 return;
242 }
243
244 wxString targetRuleName;
245 bool foundRule = false;
246
247 try
248 {
249 std::string utf8( text.mb_str( wxConvUTF8 ) );
250 DRC_RULES_LEXER lex( utf8, _( "DRC rules" ) );
251
252 struct RuleFrame
253 {
254 int depth;
255 int startLine;
256 int startCol;
257 wxString name;
258 };
259
260 std::vector<RuleFrame> ruleStack;
261 int depth = 0;
262
263 for( ;; )
264 {
265 int tok = lex.NextTok();
266
267 if( tok == DSN_EOF )
268 break;
269
270 if( tok == DSN_LEFT )
271 {
272 int leftLine = lex.CurLineNumber();
273 int leftCol = lex.CurOffset();
274
275 depth++;
276
277 int sub = lex.NextTok();
278
279 if( sub == DRCRULE_T::T_rule )
280 {
281 wxString name;
282 int nameTok = lex.NextTok();
283
284 if( DSNLEXER::IsSymbol( nameTok ) )
285 name = wxString::FromUTF8( lex.CurText() );
286
287 ruleStack.push_back( { depth, leftLine, leftCol, name } );
288 }
289 else if( sub == DSN_LEFT )
290 {
291 depth++;
292 }
293 else if( sub == DSN_RIGHT )
294 {
295 depth--;
296 }
297 else if( sub == DSN_EOF )
298 {
299 break;
300 }
301 }
302 else if( tok == DSN_RIGHT )
303 {
304 if( !ruleStack.empty() && ruleStack.back().depth == depth )
305 {
306 RuleFrame opened = ruleStack.back();
307 ruleStack.pop_back();
308
309 int endLine = lex.CurLineNumber();
310 int endCol = lex.CurOffset();
311
312 bool afterStart = cursorLine > opened.startLine
313 || ( cursorLine == opened.startLine && cursorCol >= opened.startCol );
314 bool beforeEnd = cursorLine < endLine || ( cursorLine == endLine && cursorCol <= endCol );
315
316 if( afterStart && beforeEnd )
317 {
318 targetRuleName = opened.name;
319 foundRule = true;
320 break;
321 }
322 }
323
324 depth--;
325 }
326 }
327 }
328 catch( IO_ERROR& e )
329 {
330 m_errorsReport->Report( wxString::Format( _( "Could not scan rules text: %s" ), e.What() ),
332 m_errorsReport->Flush();
333 return;
334 }
335
336 if( !foundRule )
337 {
338 m_errorsReport->Report( _( "Place the cursor inside a (rule ...) block to show matching "
339 "items." ),
341 m_errorsReport->Flush();
342 return;
343 }
344
345 if( targetRuleName.IsEmpty() )
346 {
347 m_errorsReport->Report( _( "The rule under the cursor has no name; add a name to identify "
348 "it." ),
350 m_errorsReport->Flush();
351 return;
352 }
353
354 std::shared_ptr<DRC_RULE> targetRule;
355
356 for( const auto& rule : rules )
357 {
358 if( rule->m_Name == targetRuleName )
359 {
360 targetRule = rule;
361 break;
362 }
363 }
364
365 if( !targetRule )
366 {
367 m_errorsReport->Report( wxString::Format( _( "Rule '%s' could not be located after "
368 "parsing." ),
369 targetRuleName ),
371 m_errorsReport->Flush();
372 return;
373 }
374
375 if( targetRule->m_Condition && !targetRule->m_Condition->GetExpression().IsEmpty()
376 && !targetRule->m_Condition->Compile( m_errorsReport ) )
377 {
378 m_errorsReport->Flush();
379 return;
380 }
381
382 std::shared_ptr<DRC_ENGINE> engine = m_frame->GetBoard()->GetDesignSettings().m_DRCEngine;
383
384 if( !engine )
385 {
386 m_errorsReport->Report( _( "DRC engine not available." ), RPT_SEVERITY_ERROR );
387 m_errorsReport->Flush();
388 return;
389 }
390
391 std::vector<BOARD_ITEM*> allMatches = engine->GetItemsMatchingRule( targetRule, m_errorsReport );
392
393 std::vector<BOARD_ITEM*> matches;
394
395 for( BOARD_ITEM* item : allMatches )
396 {
397 switch( item->Type() )
398 {
399 case PCB_NETINFO_T:
400 case PCB_GENERATOR_T:
401 case PCB_GROUP_T: continue;
402 default: matches.push_back( item ); break;
403 }
404 }
405
406 m_frame->FocusOnItems( matches );
407
408 m_errorsReport->Report(
409 wxString::Format( _( "Rule '%s': %zu matching item(s)." ), targetRule->m_Name, matches.size() ),
411 m_errorsReport->Flush();
412}
413
414
415void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
416{
417 if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKey() == ' ' )
418 {
419 // This is just a short-cut for do-auto-complete
420 }
421 else
422 {
424 }
425
426 m_textEditor->SearchAnchor();
427
428 int currentPos = m_textEditor->GetCurrentPos();
429 int startPos = 0;
430
431 for( int line = m_textEditor->LineFromPosition( currentPos ); line > 0; line-- )
432 {
433 int lineStart = m_textEditor->PositionFromLine( line );
434 wxString beginning = m_textEditor->GetTextRange( lineStart, lineStart + 10 );
435
436 if( beginning.StartsWith( wxT( "(rule " ) ) )
437 {
438 startPos = lineStart;
439 break;
440 }
441 }
442
443 enum
444 {
445 NO_CONTEXT,
446 STRING,
447 SEXPR_OPEN,
448 SEXPR_TOKEN,
449 SEXPR_STRING,
450 STRUCT_REF
451 };
452
453 auto isDisallowToken =
454 []( const wxString& token ) -> bool
455 {
456 return token == wxT( "blind_via" )
457 || token == wxT( "buried_via" )
458 || token == wxT( "graphic" )
459 || token == wxT( "hole" )
460 || token == wxT( "micro_via" )
461 || token == wxT( "pad" )
462 || token == wxT( "text" )
463 || token == wxT( "through_via" )
464 || token == wxT( "track" )
465 || token == wxT( "via" )
466 || token == wxT( "zone" );
467 };
468
469 auto isConstraintTypeToken =
470 []( const wxString& token ) -> bool
471 {
472 return token == wxT( "annular_width" )
473 || token == wxT( "assertion" )
474 || token == wxT( "clearance" )
475 || token == wxT( "connection_width" )
476 || token == wxT( "courtyard_clearance" )
477 || token == wxT( "diff_pair_gap" )
478 || token == wxT( "diff_pair_uncoupled" )
479 || token == wxT( "disallow" )
480 || token == wxT( "edge_clearance" )
481 || token == wxT( "length" )
482 || token == wxT( "hole_clearance" )
483 || token == wxT( "hole_size" )
484 || token == wxT( "hole_to_hole" )
485 || token == wxT( "min_resolved_spokes" )
486 || token == wxT( "physical_clearance" )
487 || token == wxT( "physical_hole_clearance" )
488 || token == wxT( "silk_clearance" )
489 || token == wxT( "skew" )
490 || token == wxT( "solder_mask_expansion" )
491 || token == wxT( "solder_paste_abs_margin" )
492 || token == wxT( "solder_paste_rel_margin" )
493 || token == wxT( "text_height" )
494 || token == wxT( "text_thickness" )
495 || token == wxT( "thermal_relief_gap" )
496 || token == wxT( "thermal_spoke_width" )
497 || token == wxT( "track_width" )
498 || token == wxT( "track_angle" )
499 || token == wxT( "track_segment_length" )
500 || token == wxT( "via_count" )
501 || token == wxT( "via_diameter" )
502 || token == wxT( "zone_connection" );
503 };
504
505 std::stack<wxString> sexprs;
506 wxString partial;
507 wxString last;
508 wxString constraintType;
509 int context = NO_CONTEXT;
510 int expr_context = NO_CONTEXT;
511
512 for( int i = startPos; i < currentPos; ++i )
513 {
514 wxChar c = m_textEditor->GetCharAt( i );
515
516 if( c == '\\' )
517 {
518 i++; // skip escaped char
519 }
520 else if( context == STRING )
521 {
522 if( c == '"' )
523 {
524 context = NO_CONTEXT;
525 }
526 else
527 {
528 if( expr_context == STRING )
529 {
530 if( c == '\'' )
531 expr_context = NO_CONTEXT;
532 else
533 partial += c;
534 }
535 else if( c == '\'' )
536 {
537 last = partial;
538 partial = wxEmptyString;
539 expr_context = STRING;
540 }
541 else if( c == '.' )
542 {
543 partial = wxEmptyString;
544 expr_context = STRUCT_REF;
545 }
546 else
547 {
548 partial += c;
549 }
550 }
551 }
552 else if( c == '"' )
553 {
554 last = partial;
555 partial = wxEmptyString;
556 context = STRING;
557 }
558 else if( c == '(' )
559 {
560 if( context == SEXPR_OPEN && !partial.IsEmpty() )
561 {
562 m_textEditor->AutoCompCancel();
563 sexprs.push( partial );
564 }
565
566 partial = wxEmptyString;
567 context = SEXPR_OPEN;
568 }
569 else if( c == ')' )
570 {
571 while( !sexprs.empty() && ( sexprs.top() == wxT( "assertion" )
572 || sexprs.top() == wxT( "disallow" )
573 || isDisallowToken( sexprs.top() )
574 || sexprs.top() == wxT( "min_resolved_spokes" )
575 || sexprs.top() == wxT( "zone_connection" ) ) )
576 {
577 sexprs.pop();
578 }
579
580 if( !sexprs.empty() )
581 {
582 // Ignore argument-less tokens
583 if( partial == wxT( "within_diff_pairs" ) )
584 {
585 partial = wxEmptyString;
586 }
587 else
588 {
589 if( sexprs.top() == wxT( "constraint" ) )
590 {
591 constraintType = wxEmptyString;
592 }
593
594 sexprs.pop();
595 }
596 }
597
598 context = NO_CONTEXT;
599 }
600 else if( c == ' ' )
601 {
602 if( context == SEXPR_OPEN && !partial.IsEmpty() )
603 {
604 m_textEditor->AutoCompCancel();
605 sexprs.push( partial );
606
607 if( partial == wxT( "constraint" )
608 || partial == wxT( "layer" )
609 || partial == wxT( "severity" ) )
610 {
611 context = SEXPR_TOKEN;
612 }
613 else if( partial == wxT( "rule" )
614 || partial == wxT( "condition" ) )
615 {
616 context = SEXPR_STRING;
617 }
618 else
619 {
620 context = NO_CONTEXT;
621 }
622
623 partial = wxEmptyString;
624 continue;
625 }
626 else if( partial == wxT( "disallow" )
627 || isDisallowToken( partial )
628 || partial == wxT( "min_resolved_spokes" )
629 || partial == wxT( "zone_connection" ) )
630 {
631 m_textEditor->AutoCompCancel();
632 sexprs.push( partial );
633
634 partial = wxEmptyString;
635 context = SEXPR_TOKEN;
636 continue;
637 }
638 else if( partial == wxT( "assertion" ) )
639 {
640 m_textEditor->AutoCompCancel();
641 sexprs.push( partial );
642
643 partial = wxEmptyString;
644 context = SEXPR_STRING;
645 continue;
646 }
647 else if( isConstraintTypeToken( partial ) )
648 {
649 constraintType = partial;
650 }
651
652 context = NO_CONTEXT;
653 }
654 else
655 {
656 partial += c;
657 }
658 }
659
660 wxString tokens;
661
662 if( context == SEXPR_OPEN )
663 {
664 if( sexprs.empty() )
665 {
666 tokens = wxT( "rule|"
667 "version" );
668 }
669 else if( sexprs.top() == wxT( "rule" ) )
670 {
671 tokens = wxT( "condition|"
672 "constraint|"
673 "layer|"
674 "severity" );
675 }
676 else if( sexprs.top() == wxT( "constraint" ) )
677 {
678 if( constraintType == wxT( "skew" ) )
679 tokens = wxT( "max|min|opt|within_diff_pairs" );
680 else
681 tokens = wxT( "max|min|opt" );
682 }
683 }
684 else if( context == SEXPR_TOKEN )
685 {
686 if( sexprs.empty() )
687 {
688 /* badly formed grammar */
689 }
690 else if( sexprs.top() == wxT( "constraint" ) )
691 {
692 tokens = wxT( "annular_width|"
693 "assertion|"
694 "bridged_mask|"
695 "clearance|"
696 "connection_width|"
697 "courtyard_clearance|"
698 "creepage|"
699 "diff_pair_gap|"
700 "diff_pair_uncoupled|"
701 "disallow|"
702 "edge_clearance|"
703 "length|"
704 "hole_clearance|"
705 "hole_size|"
706 "hole_to_hole|"
707 "min_resolved_spokes|"
708 "physical_clearance|"
709 "physical_hole_clearance|"
710 "silk_clearance|"
711 "skew|"
712 "solder_mask_expansion|"
713 "solder_paste_abs_margin|"
714 "solder_paste_rel_margin|"
715 "text_height|"
716 "text_thickness|"
717 "thermal_relief_gap|"
718 "thermal_spoke_width|"
719 "track_width|"
720 "track_angle|"
721 "track_segment_length|"
722 "via_count|"
723 "via_diameter|"
724 "zone_connection" );
725 }
726 else if( sexprs.top() == wxT( "disallow" ) || isDisallowToken( sexprs.top() ) )
727 {
728 tokens = wxT( "blind_via|"
729 "buried_via|"
730 "footprint|"
731 "graphic|"
732 "hole|"
733 "micro_via|"
734 "pad|"
735 "text|"
736 "through_via|"
737 "track|"
738 "via|"
739 "zone" );
740 }
741 else if( sexprs.top() == wxT( "zone_connection" ) )
742 {
743 tokens = wxT( "none|solid|thermal_reliefs" );
744 }
745 else if( sexprs.top() == wxT( "min_resolved_spokes" ) )
746 {
747 tokens = wxT( "0|1|2|3|4" );
748 }
749 else if( sexprs.top() == wxT( "layer" ) )
750 {
751 tokens = wxT( "inner|outer|\"x\"" );
752 }
753 else if( sexprs.top() == wxT( "severity" ) )
754 {
755 tokens = wxT( "warning|error|ignore|exclusion" );
756 }
757 }
758 else if( context == SEXPR_STRING && !sexprs.empty()
759 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
760 {
761 m_textEditor->AddText( wxT( "\"" ) );
762 }
763 else if( context == STRING && !sexprs.empty()
764 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
765 {
766 if( expr_context == STRUCT_REF )
767 {
769 std::set<wxString> propNames;
770
771 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
772 {
773 const std::vector<PROPERTY_BASE*>& props = propMgr.GetProperties( cls.type );
774
775 for( PROPERTY_BASE* prop : props )
776 {
777 // TODO: It would be nice to replace IsHiddenFromRulesEditor with a nickname
778 // system, so that two different properies don't need to be created. This is
779 // a bigger change than I want to make right now, though.
780 if( prop->IsHiddenFromRulesEditor() )
781 continue;
782
783 wxString ref( prop->Name() );
784 ref.Replace( wxT( " " ), wxT( "_" ) );
785 propNames.insert( ref );
786 }
787 }
788
789 for( const wxString& propName : propNames )
790 tokens += wxT( "|" ) + propName;
791
793
794 for( const wxString& funcSig : functions.GetSignatures() )
795 {
796 if( !funcSig.Contains( "DEPRECATED" ) )
797 tokens += wxT( "|" ) + funcSig;
798 }
799 }
800 else if( expr_context == STRING )
801 {
802 if( m_netClassRegex.Matches( last ) )
803 {
804 BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
805 std::shared_ptr<NET_SETTINGS>& netSettings = bds.m_NetSettings;
806
807 for( const auto& [name, netclass] : netSettings->GetNetclasses() )
808 tokens += wxT( "|" ) + name;
809 }
810 else if( m_netNameRegex.Matches( last ) )
811 {
812 BOARD* board = m_frame->GetBoard();
813
814 for( const wxString& netnameCandidate : board->GetNetClassAssignmentCandidates() )
815 tokens += wxT( "|" ) + netnameCandidate;
816 }
817 else if( m_typeRegex.Matches( last ) )
818 {
819 tokens = wxT( "Bitmap|"
820 "Dimension|"
821 "Footprint|"
822 "Graphic|"
823 "Group|"
824 "Leader|"
825 "Pad|"
826 "Target|"
827 "Text|"
828 "Text Box|"
829 "Track|"
830 "Via|"
831 "Zone" );
832 }
833 else if( m_viaTypeRegex.Matches( last ) )
834 {
835 tokens = wxT( "Through|"
836 "Blind|"
837 "Buried|"
838 "Micro" );
839 }
840 else if( m_padTypeRegex.Matches( last ) )
841 {
842 tokens = wxT( "Through-hole|"
843 "SMD|"
844 "Edge connector|"
845 "NPTH, mechanical" );
846 }
847 else if( m_pinTypeRegex.Matches( last ) )
848 {
849 tokens = wxT( "Input|"
850 "Output|"
851 "Bidirectional|"
852 "Tri-state|"
853 "Passive|"
854 "Free|"
855 "Unspecified|"
856 "Power input|"
857 "Power output|"
858 "Open collector|"
859 "Open emitter|"
860 "Unconnected" );
861 }
862 else if( m_fabPropRegex.Matches( last ) )
863 {
864 tokens = wxT( "None|"
865 "BGA pad|"
866 "Fiducial, global to board|"
867 "Fiducial, local to footprint|"
868 "Test point pad|"
869 "Heatsink pad|"
870 "Castellated pad" );
871 }
872 else if( m_shapeRegex.Matches( last ) )
873 {
874 tokens = wxT( "Segment|"
875 "Rectangle|"
876 "Arc|"
877 "Circle|"
878 "Polygon|"
879 "Bezier" );
880 }
881 else if( m_padShapeRegex.Matches( last ) )
882 {
883 tokens = wxT( "Circle|"
884 "Rectangle|"
885 "Oval|"
886 "Trapezoid|"
887 "Rounded rectangle|"
888 "Chamfered rectangle|"
889 "Custom" );
890 }
891 else if( m_padConnectionsRegex.Matches( last ) )
892 {
893 tokens = wxT( "Inherited|"
894 "None|"
895 "Solid|"
896 "Thermal reliefs|"
897 "Thermal reliefs for PTH" );
898 }
899 else if( m_zoneConnStyleRegex.Matches( last ) )
900 {
901 tokens = wxT( "Inherited|"
902 "None|"
903 "Solid|"
904 "Thermal reliefs" );
905 }
906 else if( m_lineStyleRegex.Matches( last ) )
907 {
908 tokens = wxT( "Default|"
909 "Solid|"
910 "Dashed|"
911 "Dotted|"
912 "Dash-Dot|"
913 "Dash-Dot-Dot" );
914 }
915 else if( m_hJustRegex.Matches( last ) )
916 {
917 tokens = wxT( "Left|"
918 "Center|"
919 "Right" );
920 }
921 else if( m_vJustRegex.Matches( last ) )
922 {
923 tokens = wxT( "Top|"
924 "Center|"
925 "Bottom" );
926 }
927 }
928 }
929
930 if( !tokens.IsEmpty() )
931 m_scintillaTricks->DoAutocomplete( partial, wxSplit( tokens, '|' ) );
932}
933
934
935void PANEL_SETUP_RULES::OnCompile( wxCommandEvent& event )
936{
937 m_errorsReport->Clear();
938
939 try
940 {
941 std::vector<std::shared_ptr<DRC_RULE>> dummyRules;
942
943 std::function<bool( wxString* )> resolver =
944 [&]( wxString* token ) -> bool
945 {
946 if( m_frame->GetBoard()->ResolveTextVar( token, 0 ) )
947 return true;
948
949 return false;
950 };
951
952 wxString rulesText = m_textEditor->GetText();
953 rulesText = m_frame->GetBoard()->ConvertCrossReferencesToKIIDs( rulesText );
954 rulesText = ExpandTextVars( rulesText, &resolver );
955
956 DRC_RULES_PARSER parser( rulesText, _( "DRC rules" ) );
957
958 parser.Parse( dummyRules, m_errorsReport );
959 checkPlausibility( dummyRules );
960 }
961 catch( PARSE_ERROR& pe )
962 {
963 m_errorsReport->Report( wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>%s" ),
964 _( "ERROR:" ),
965 pe.lineNumber,
966 pe.byteIndex,
967 pe.ParseProblem(),
968 wxEmptyString ),
970 }
971
972 m_errorsReport->Flush();
973}
974
975
976void PANEL_SETUP_RULES::checkPlausibility( const std::vector<std::shared_ptr<DRC_RULE>>& aRules )
977{
978 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( !bds.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 wxFileName rulesFile( m_frame->GetDesignRulesPath() );
1080
1081 if( rulesFile.FileExists() )
1082 {
1083 wxTextFile file( rulesFile.GetFullPath() );
1084
1085 if( file.Open() )
1086 {
1087 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
1088 {
1090 m_textEditor->AddText( str << '\n' );
1091 }
1092
1093 m_textEditor->EmptyUndoBuffer();
1094
1095 wxCommandEvent dummy;
1096 OnCompile( dummy );
1097 }
1098 }
1099 else
1100 {
1101 m_textEditor->AddText( wxT( "(version 1)\n" ) );
1102 }
1103
1104 m_originalText = m_textEditor->GetText();
1105
1106 if( m_frame->Prj().IsNullProject() )
1107 {
1108 m_textEditor->ClearAll();
1109 m_textEditor->AddText( _( "Design rules cannot be added without a project" ) );
1110 m_textEditor->Disable();
1111 }
1112
1113 return true;
1114}
1115
1116
1118{
1119 if( m_originalText == m_textEditor->GetText() )
1120 return true;
1121
1122 if( m_frame->Prj().IsNullProject() )
1123 return true;
1124
1125 wxString rulesFilepath = m_frame->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:110
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:84
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:323
std::set< wxString > GetNetClassAssignmentCandidates() const
Return the set of netname candidates for netclass assignment.
Definition board.cpp:2747
const wxString & GetFileName() const
Definition board.h:360
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:745
PROJECT * GetProject() const
Definition board.h:587
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1101
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:986
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:327
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:313
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:541
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:411
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:63
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:278
This file is part of the common library.
@ DSN_LEFT
Definition dsnlexer.h:67
@ DSN_RIGHT
Definition dsnlexer.h:66
@ DSN_EOF
Definition dsnlexer.h:69
#define _(s)
static FILENAME_RESOLVER * resolver
static const std::string DesignRulesFileExtension
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
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:88
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:108
@ PCB_NETINFO_T
class NETINFO_ITEM, a description of a net
Definition typeinfo.h:107
Definition of file extensions used in Kicad.