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 <confirm.h>
28#include <pcb_edit_frame.h>
29#include <pcbexpr_evaluator.h>
30#include <board.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>
40#include <drc/drc_rule_parser.h>
41#include <tools/drc_tool.h>
42#include <pgm_base.h>
44#include <regex>
45#include <unordered_map>
46
47PANEL_SETUP_RULES::PANEL_SETUP_RULES( wxWindow* aParentWindow, PCB_EDIT_FRAME* aFrame ) :
48 PANEL_SETUP_RULES_BASE( aParentWindow ),
49 m_frame( aFrame ),
50 m_scintillaTricks( nullptr ),
51 m_helpWindow( nullptr )
52{
53 m_scintillaTricks = new SCINTILLA_TRICKS( m_textEditor, wxT( "()" ), false,
54 // onAcceptFn
55 [this]( wxKeyEvent& aEvent )
56 {
57 wxPostEvent( PAGED_DIALOG::GetDialog( this ),
58 wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
59 },
60 // onCharFn
61 [this]( wxStyledTextEvent& aEvent )
62 {
63 onScintillaCharAdded( aEvent );
64 } );
65
66 m_textEditor->AutoCompSetSeparator( '|' );
67
68 m_netClassRegex.Compile( "^NetClass\\s*[!=]=\\s*$", wxRE_ADVANCED );
69 m_netNameRegex.Compile( "^NetName\\s*[!=]=\\s*$", wxRE_ADVANCED );
70 m_typeRegex.Compile( "^Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
71 m_viaTypeRegex.Compile( "^Via_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
72 m_padTypeRegex.Compile( "^Pad_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
73 m_pinTypeRegex.Compile( "^Pin_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
74 m_fabPropRegex.Compile( "^Fabrication_Property\\s*[!=]=\\s*$", wxRE_ADVANCED );
75 m_shapeRegex.Compile( "^Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
76 m_padShapeRegex.Compile( "^Pad_Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
77 m_padConnectionsRegex.Compile( "^Pad_Connections\\s*[!=]=\\s*$", wxRE_ADVANCED );
78 m_zoneConnStyleRegex.Compile( "^Zone_Connection_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
79 m_lineStyleRegex.Compile( "^Line_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
80 m_hJustRegex.Compile( "^Horizontal_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
81 m_vJustRegex.Compile( "^Vertical_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
82
84
85 m_textEditor->SetZoom( Pgm().GetCommonSettings()->m_Appearance.text_editor_zoom );
86
87 m_textEditor->UsePopUp( 0 );
88 m_textEditor->Bind( wxEVT_CHAR_HOOK, &PANEL_SETUP_RULES::onCharHook, this );
89}
90
91
93{
94 m_textEditor->Unbind( wxEVT_CHAR_HOOK, &PANEL_SETUP_RULES::onCharHook, this );
96
97 delete m_scintillaTricks;
98 m_scintillaTricks = nullptr;
99
100 if( m_helpWindow )
101 m_helpWindow->Destroy();
102};
103
104
105void PANEL_SETUP_RULES::onCharHook( wxKeyEvent& aEvent )
106{
107 if( aEvent.GetKeyCode() == WXK_ESCAPE && !m_textEditor->AutoCompActive() )
108 {
109 if( m_originalText != m_textEditor->GetText() )
110 {
111 if( IsOK( wxGetTopLevelParent( this ), _( "Cancel Changes?" ) ) )
112 {
113 m_textEditor->SetText( m_originalText );
114 m_textEditor->SelectAll();
115 }
116
117 return;
118 }
119 }
120
121 aEvent.Skip();
122}
123
124
125void PANEL_SETUP_RULES::OnContextMenu(wxMouseEvent &event)
126{
127 wxMenu menu;
128
129 menu.Append( wxID_UNDO, _( "Undo" ) );
130 menu.Append( wxID_REDO, _( "Redo" ) );
131
132 menu.AppendSeparator();
133
134 menu.Append( 1, _( "Cut" ) ); // Don't use wxID_CUT, wxID_COPY, etc. On Mac (at least),
135 menu.Append( 2, _( "Copy" ) ); // wxWidgets never delivers them to us.
136 menu.Append( 3, _( "Paste" ) );
137 menu.Append( 4, _( "Delete" ) );
138
139 menu.AppendSeparator();
140
141 menu.Append( 5, _( "Select All" ) );
142
143 menu.AppendSeparator();
144
145 menu.Append( wxID_ZOOM_IN, _( "Zoom In" ) );
146 menu.Append( wxID_ZOOM_OUT, _( "Zoom Out" ) );
147
148
149 switch( GetPopupMenuSelectionFromUser( menu ) )
150 {
151 case wxID_UNDO:
152 m_textEditor->Undo();
153 break;
154 case wxID_REDO:
155 m_textEditor->Redo();
156 break;
157
158 case 1:
159 m_textEditor->Cut();
160 break;
161 case 2:
162 m_textEditor->Copy();
163 break;
164 case 3:
165 m_textEditor->Paste();
166 break;
167 case 4:
168 {
169 long from, to;
170 m_textEditor->GetSelection( &from, &to );
171
172 if( to > from )
173 m_textEditor->DeleteRange( from, to );
174
175 break;
176 }
177
178 case 5:
179 m_textEditor->SelectAll();
180 break;
181
182 case wxID_ZOOM_IN:
183 m_textEditor->ZoomIn();
184 break;
185 case wxID_ZOOM_OUT:
186 m_textEditor->ZoomOut();
187 break;
188 }
189}
190
191
192void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
193{
194 if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKey() == ' ' )
195 {
196 // This is just a short-cut for do-auto-complete
197 }
198 else
199 {
201 }
202
203 m_textEditor->SearchAnchor();
204
205 int currentPos = m_textEditor->GetCurrentPos();
206 int startPos = 0;
207
208 for( int line = m_textEditor->LineFromPosition( currentPos ); line > 0; line-- )
209 {
210 int lineStart = m_textEditor->PositionFromLine( line );
211 wxString beginning = m_textEditor->GetTextRange( lineStart, lineStart + 10 );
212
213 if( beginning.StartsWith( wxT( "(rule " ) ) )
214 {
215 startPos = lineStart;
216 break;
217 }
218 }
219
220 enum
221 {
222 NO_CONTEXT,
223 STRING,
224 SEXPR_OPEN,
225 SEXPR_TOKEN,
226 SEXPR_STRING,
227 STRUCT_REF
228 };
229
230 auto isDisallowToken =
231 []( const wxString& token ) -> bool
232 {
233 return token == wxT( "blind_via" )
234 || token == wxT( "buried_via" )
235 || token == wxT( "graphic" )
236 || token == wxT( "hole" )
237 || token == wxT( "micro_via" )
238 || token == wxT( "pad" )
239 || token == wxT( "text" )
240 || token == wxT( "through_via" )
241 || token == wxT( "track" )
242 || token == wxT( "via" )
243 || token == wxT( "zone" );
244 };
245
246 auto isConstraintTypeToken =
247 []( const wxString& token ) -> bool
248 {
249 return token == wxT( "annular_width" )
250 || token == wxT( "assertion" )
251 || token == wxT( "clearance" )
252 || token == wxT( "connection_width" )
253 || token == wxT( "courtyard_clearance" )
254 || token == wxT( "diff_pair_gap" )
255 || token == wxT( "diff_pair_uncoupled" )
256 || token == wxT( "disallow" )
257 || token == wxT( "edge_clearance" )
258 || token == wxT( "length" )
259 || token == wxT( "hole_clearance" )
260 || token == wxT( "hole_size" )
261 || token == wxT( "hole_to_hole" )
262 || token == wxT( "min_resolved_spokes" )
263 || token == wxT( "physical_clearance" )
264 || token == wxT( "physical_hole_clearance" )
265 || token == wxT( "silk_clearance" )
266 || token == wxT( "skew" )
267 || token == wxT( "solder_mask_expansion" )
268 || token == wxT( "solder_paste_abs_margin" )
269 || token == wxT( "solder_paste_rel_margin" )
270 || token == wxT( "text_height" )
271 || token == wxT( "text_thickness" )
272 || token == wxT( "thermal_relief_gap" )
273 || token == wxT( "thermal_spoke_width" )
274 || token == wxT( "track_width" )
275 || token == wxT( "track_angle" )
276 || token == wxT( "track_segment_length" )
277 || token == wxT( "via_count" )
278 || token == wxT( "via_diameter" )
279 || token == wxT( "zone_connection" );
280 };
281
282 std::stack<wxString> sexprs;
283 wxString partial;
284 wxString last;
285 wxString constraintType;
286 int context = NO_CONTEXT;
287 int expr_context = NO_CONTEXT;
288
289 for( int i = startPos; i < currentPos; ++i )
290 {
291 wxChar c = m_textEditor->GetCharAt( i );
292
293 if( c == '\\' )
294 {
295 i++; // skip escaped char
296 }
297 else if( context == STRING )
298 {
299 if( c == '"' )
300 {
301 context = NO_CONTEXT;
302 }
303 else
304 {
305 if( expr_context == STRING )
306 {
307 if( c == '\'' )
308 expr_context = NO_CONTEXT;
309 else
310 partial += c;
311 }
312 else if( c == '\'' )
313 {
314 last = partial;
315 partial = wxEmptyString;
316 expr_context = STRING;
317 }
318 else if( c == '.' )
319 {
320 partial = wxEmptyString;
321 expr_context = STRUCT_REF;
322 }
323 else
324 {
325 partial += c;
326 }
327 }
328 }
329 else if( c == '"' )
330 {
331 last = partial;
332 partial = wxEmptyString;
333 context = STRING;
334 }
335 else if( c == '(' )
336 {
337 if( context == SEXPR_OPEN && !partial.IsEmpty() )
338 {
339 m_textEditor->AutoCompCancel();
340 sexprs.push( partial );
341 }
342
343 partial = wxEmptyString;
344 context = SEXPR_OPEN;
345 }
346 else if( c == ')' )
347 {
348 while( !sexprs.empty() && ( sexprs.top() == wxT( "assertion" )
349 || sexprs.top() == wxT( "disallow" )
350 || isDisallowToken( sexprs.top() )
351 || sexprs.top() == wxT( "min_resolved_spokes" )
352 || sexprs.top() == wxT( "zone_connection" ) ) )
353 {
354 sexprs.pop();
355 }
356
357 if( !sexprs.empty() )
358 {
359 // Ignore argument-less tokens
360 if( partial == wxT( "within_diff_pairs" ) )
361 {
362 partial = wxEmptyString;
363 }
364 else
365 {
366 if( sexprs.top() == wxT( "constraint" ) )
367 {
368 constraintType = wxEmptyString;
369 }
370
371 sexprs.pop();
372 }
373 }
374
375 context = NO_CONTEXT;
376 }
377 else if( c == ' ' )
378 {
379 if( context == SEXPR_OPEN && !partial.IsEmpty() )
380 {
381 m_textEditor->AutoCompCancel();
382 sexprs.push( partial );
383
384 if( partial == wxT( "constraint" )
385 || partial == wxT( "layer" )
386 || partial == wxT( "severity" ) )
387 {
388 context = SEXPR_TOKEN;
389 }
390 else if( partial == wxT( "rule" )
391 || partial == wxT( "condition" ) )
392 {
393 context = SEXPR_STRING;
394 }
395 else
396 {
397 context = NO_CONTEXT;
398 }
399
400 partial = wxEmptyString;
401 continue;
402 }
403 else if( partial == wxT( "disallow" )
404 || isDisallowToken( partial )
405 || partial == wxT( "min_resolved_spokes" )
406 || partial == wxT( "zone_connection" ) )
407 {
408 m_textEditor->AutoCompCancel();
409 sexprs.push( partial );
410
411 partial = wxEmptyString;
412 context = SEXPR_TOKEN;
413 continue;
414 }
415 else if( partial == wxT( "assertion" ) )
416 {
417 m_textEditor->AutoCompCancel();
418 sexprs.push( partial );
419
420 partial = wxEmptyString;
421 context = SEXPR_STRING;
422 continue;
423 }
424 else if( isConstraintTypeToken( partial ) )
425 {
426 constraintType = partial;
427 }
428
429 context = NO_CONTEXT;
430 }
431 else
432 {
433 partial += c;
434 }
435 }
436
437 wxString tokens;
438
439 if( context == SEXPR_OPEN )
440 {
441 if( sexprs.empty() )
442 {
443 tokens = wxT( "rule|"
444 "version" );
445 }
446 else if( sexprs.top() == wxT( "rule" ) )
447 {
448 tokens = wxT( "condition|"
449 "constraint|"
450 "layer|"
451 "severity" );
452 }
453 else if( sexprs.top() == wxT( "constraint" ) )
454 {
455 if( constraintType == wxT( "skew" ) )
456 tokens = wxT( "max|min|opt|within_diff_pairs" );
457 else
458 tokens = wxT( "max|min|opt" );
459 }
460 }
461 else if( context == SEXPR_TOKEN )
462 {
463 if( sexprs.empty() )
464 {
465 /* badly formed grammar */
466 }
467 else if( sexprs.top() == wxT( "constraint" ) )
468 {
469 tokens = wxT( "annular_width|"
470 "assertion|"
471 "bridged_mask|"
472 "clearance|"
473 "connection_width|"
474 "courtyard_clearance|"
475 "creepage|"
476 "diff_pair_gap|"
477 "diff_pair_uncoupled|"
478 "disallow|"
479 "edge_clearance|"
480 "length|"
481 "hole_clearance|"
482 "hole_size|"
483 "hole_to_hole|"
484 "min_resolved_spokes|"
485 "physical_clearance|"
486 "physical_hole_clearance|"
487 "silk_clearance|"
488 "skew|"
489 "solder_mask_expansion|"
490 "solder_paste_abs_margin|"
491 "solder_paste_rel_margin|"
492 "text_height|"
493 "text_thickness|"
494 "thermal_relief_gap|"
495 "thermal_spoke_width|"
496 "track_width|"
497 "track_angle|"
498 "track_segment_length|"
499 "via_count|"
500 "via_diameter|"
501 "zone_connection" );
502 }
503 else if( sexprs.top() == wxT( "disallow" ) || isDisallowToken( sexprs.top() ) )
504 {
505 tokens = wxT( "buried_via|"
506 "footprint|"
507 "graphic|"
508 "hole|"
509 "micro_via|"
510 "pad|"
511 "text|"
512 "track|"
513 "via|"
514 "zone" );
515 }
516 else if( sexprs.top() == wxT( "zone_connection" ) )
517 {
518 tokens = wxT( "none|solid|thermal_reliefs" );
519 }
520 else if( sexprs.top() == wxT( "min_resolved_spokes" ) )
521 {
522 tokens = wxT( "0|1|2|3|4" );
523 }
524 else if( sexprs.top() == wxT( "layer" ) )
525 {
526 tokens = wxT( "inner|outer|\"x\"" );
527 }
528 else if( sexprs.top() == wxT( "severity" ) )
529 {
530 tokens = wxT( "warning|error|ignore|exclusion" );
531 }
532 }
533 else if( context == SEXPR_STRING && !sexprs.empty()
534 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
535 {
536 m_textEditor->AddText( wxT( "\"" ) );
537 }
538 else if( context == STRING && !sexprs.empty()
539 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
540 {
541 if( expr_context == STRUCT_REF )
542 {
544 std::set<wxString> propNames;
545
546 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
547 {
548 const std::vector<PROPERTY_BASE*>& props = propMgr.GetProperties( cls.type );
549
550 for( PROPERTY_BASE* prop : props )
551 {
552 // TODO: It would be nice to replace IsHiddenFromRulesEditor with a nickname
553 // system, so that two different properies don't need to be created. This is
554 // a bigger change than I want to make right now, though.
555 if( prop->IsHiddenFromRulesEditor() )
556 continue;
557
558 wxString ref( prop->Name() );
559 ref.Replace( wxT( " " ), wxT( "_" ) );
560 propNames.insert( ref );
561 }
562 }
563
564 for( const wxString& propName : propNames )
565 tokens += wxT( "|" ) + propName;
566
568
569 for( const wxString& funcSig : functions.GetSignatures() )
570 {
571 if( !funcSig.Contains( "DEPRECATED" ) )
572 tokens += wxT( "|" ) + funcSig;
573 }
574 }
575 else if( expr_context == STRING )
576 {
577 if( m_netClassRegex.Matches( last ) )
578 {
579 BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
580 std::shared_ptr<NET_SETTINGS>& netSettings = bds.m_NetSettings;
581
582 for( const auto& [name, netclass] : netSettings->GetNetclasses() )
583 tokens += wxT( "|" ) + name;
584 }
585 else if( m_netNameRegex.Matches( last ) )
586 {
587 BOARD* board = m_frame->GetBoard();
588
589 for( const wxString& netnameCandidate : board->GetNetClassAssignmentCandidates() )
590 tokens += wxT( "|" ) + netnameCandidate;
591 }
592 else if( m_typeRegex.Matches( last ) )
593 {
594 tokens = wxT( "Bitmap|"
595 "Dimension|"
596 "Footprint|"
597 "Graphic|"
598 "Group|"
599 "Leader|"
600 "Pad|"
601 "Target|"
602 "Text|"
603 "Text Box|"
604 "Track|"
605 "Via|"
606 "Zone" );
607 }
608 else if( m_viaTypeRegex.Matches( last ) )
609 {
610 tokens = wxT( "Through|"
611 "Blind|"
612 "Buried|"
613 "Micro" );
614 }
615 else if( m_padTypeRegex.Matches( last ) )
616 {
617 tokens = wxT( "Through-hole|"
618 "SMD|"
619 "Edge connector|"
620 "NPTH, mechanical" );
621 }
622 else if( m_pinTypeRegex.Matches( last ) )
623 {
624 tokens = wxT( "Input|"
625 "Output|"
626 "Bidirectional|"
627 "Tri-state|"
628 "Passive|"
629 "Free|"
630 "Unspecified|"
631 "Power input|"
632 "Power output|"
633 "Open collector|"
634 "Open emitter|"
635 "Unconnected" );
636 }
637 else if( m_fabPropRegex.Matches( last ) )
638 {
639 tokens = wxT( "None|"
640 "BGA pad|"
641 "Fiducial, global to board|"
642 "Fiducial, local to footprint|"
643 "Test point pad|"
644 "Heatsink pad|"
645 "Castellated pad" );
646 }
647 else if( m_shapeRegex.Matches( last ) )
648 {
649 tokens = wxT( "Segment|"
650 "Rectangle|"
651 "Arc|"
652 "Circle|"
653 "Polygon|"
654 "Bezier" );
655 }
656 else if( m_padShapeRegex.Matches( last ) )
657 {
658 tokens = wxT( "Circle|"
659 "Rectangle|"
660 "Oval|"
661 "Trapezoid|"
662 "Rounded rectangle|"
663 "Chamfered rectangle|"
664 "Custom" );
665 }
666 else if( m_padConnectionsRegex.Matches( last ) )
667 {
668 tokens = wxT( "Inherited|"
669 "None|"
670 "Solid|"
671 "Thermal reliefs|"
672 "Thermal reliefs for PTH" );
673 }
674 else if( m_zoneConnStyleRegex.Matches( last ) )
675 {
676 tokens = wxT( "Inherited|"
677 "None|"
678 "Solid|"
679 "Thermal reliefs" );
680 }
681 else if( m_lineStyleRegex.Matches( last ) )
682 {
683 tokens = wxT( "Default|"
684 "Solid|"
685 "Dashed|"
686 "Dotted|"
687 "Dash-Dot|"
688 "Dash-Dot-Dot" );
689 }
690 else if( m_hJustRegex.Matches( last ) )
691 {
692 tokens = wxT( "Left|"
693 "Center|"
694 "Right" );
695 }
696 else if( m_vJustRegex.Matches( last ) )
697 {
698 tokens = wxT( "Top|"
699 "Center|"
700 "Bottom" );
701 }
702 }
703 }
704
705 if( !tokens.IsEmpty() )
706 m_scintillaTricks->DoAutocomplete( partial, wxSplit( tokens, '|' ) );
707}
708
709
710void PANEL_SETUP_RULES::OnCompile( wxCommandEvent& event )
711{
712 m_errorsReport->Clear();
713
714 try
715 {
716 std::vector<std::shared_ptr<DRC_RULE>> dummyRules;
717
718 std::function<bool( wxString* )> resolver =
719 [&]( wxString* token ) -> bool
720 {
721 if( m_frame->GetBoard()->ResolveTextVar( token, 0 ) )
722 return true;
723
724 return false;
725 };
726
727 wxString rulesText = m_textEditor->GetText();
728 rulesText = m_frame->GetBoard()->ConvertCrossReferencesToKIIDs( rulesText );
729 rulesText = ExpandTextVars( rulesText, &resolver );
730
731 DRC_RULES_PARSER parser( rulesText, _( "DRC rules" ) );
732
733 parser.Parse( dummyRules, m_errorsReport );
734 checkPlausibility( dummyRules );
735 }
736 catch( PARSE_ERROR& pe )
737 {
738 m_errorsReport->Report( wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>%s" ),
739 _( "ERROR:" ),
740 pe.lineNumber,
741 pe.byteIndex,
742 pe.ParseProblem(),
743 wxEmptyString ),
745 }
746
747 m_errorsReport->Flush();
748}
749
750
751void PANEL_SETUP_RULES::checkPlausibility( const std::vector<std::shared_ptr<DRC_RULE>>& aRules )
752{
753 BOARD* board = m_frame->GetBoard();
755 LSET enabledLayers = board->GetEnabledLayers();
756
757 std::unordered_map<wxString, wxString> seenConditions;
758 std::regex netclassPattern( "NetClass\\s*[!=]=\\s*\"?([^\"\\s]+)\"?" );
759
760 for( const auto& rule : aRules )
761 {
762 wxString condition;
763
764 if( rule->m_Condition )
765 condition = rule->m_Condition->GetExpression();
766
767 condition.Trim( true ).Trim( false );
768
769 if( seenConditions.count( condition ) )
770 {
771 m_errorsReport->Report( wxString::Format( _( "Rules '%s' and '%s' share the same condition." ),
772 rule->m_Name,
773 seenConditions[condition] ),
775 }
776 else
777 {
778 seenConditions[condition] = rule->m_Name;
779 }
780
781 std::string condUtf8 = condition.ToStdString();
782 std::sregex_iterator it( condUtf8.begin(), condUtf8.end(), netclassPattern );
783 std::sregex_iterator end;
784
785 for( ; it != end; ++it )
786 {
787 wxString ncName = wxString::FromUTF8( ( *it )[1].str() );
788
789 if( !bds.m_NetSettings->HasNetclass( ncName ) )
790 {
791 m_errorsReport->Report( wxString::Format( _( "Rule '%s' references undefined netclass '%s'." ),
792 rule->m_Name,
793 ncName ),
795 }
796 }
797
798 if( !rule->m_LayerSource.IsEmpty() )
799 {
800 LSET invalid = rule->m_LayerCondition & ~enabledLayers;
801
802 if( invalid.any() )
803 {
804 wxString badLayers;
805
806 for( PCB_LAYER_ID layer : invalid.Seq() )
807 {
808 if( !badLayers.IsEmpty() )
809 badLayers += ", ";
810
811 badLayers += board->GetLayerName( layer );
812 }
813
814 m_errorsReport->Report( wxString::Format( _( "Rule '%s' references undefined layer(s): %s." ),
815 rule->m_Name,
816 badLayers ),
818 }
819 }
820 }
821}
822
823
824void PANEL_SETUP_RULES::OnErrorLinkClicked( wxHtmlLinkEvent& event )
825{
826 wxString link = event.GetLinkInfo().GetHref();
827 wxArrayString parts;
828 long line = 0, offset = 0;
829
830 wxStringSplit( link, parts, ':' );
831
832 if( parts.size() > 1 )
833 {
834 parts[0].ToLong( &line );
835 parts[1].ToLong( &offset );
836 }
837
838 int pos = m_textEditor->PositionFromLine( line - 1 ) + ( offset - 1 );
839
840 m_textEditor->GotoPos( pos );
841
842 m_textEditor->SetFocus();
843}
844
845
847{
848 wxFileName rulesFile( m_frame->GetDesignRulesPath() );
849
850 if( rulesFile.FileExists() )
851 {
852 wxTextFile file( rulesFile.GetFullPath() );
853
854 if( file.Open() )
855 {
856 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
857 {
859 m_textEditor->AddText( str << '\n' );
860 }
861
862 m_textEditor->EmptyUndoBuffer();
863
864 wxCommandEvent dummy;
865 OnCompile( dummy );
866 }
867 }
868 else
869 {
870 m_textEditor->AddText( wxT( "(version 1)\n" ) );
871 }
872
873 m_originalText = m_textEditor->GetText();
874
875 if( m_frame->Prj().IsNullProject() )
876 {
877 m_textEditor->ClearAll();
878 m_textEditor->AddText( _( "Design rules cannot be added without a project" ) );
879 m_textEditor->Disable();
880 }
881
882 return true;
883}
884
885
887{
888 if( m_originalText == m_textEditor->GetText() )
889 return true;
890
891 if( m_frame->Prj().IsNullProject() )
892 return true;
893
894 wxString rulesFilepath = m_frame->GetDesignRulesPath();
895
896 try
897 {
898 if( m_textEditor->SaveFile( rulesFilepath ) )
899 {
900 m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine( rulesFilepath );
901 return true;
902 }
903 }
904 catch( PARSE_ERROR& )
905 {
906 // Don't lock them in to the Setup dialog if they have bad rules. They've already
907 // saved them so we can allow an exit.
908 return true;
909 }
910
911 return false;
912}
913
914
915void PANEL_SETUP_RULES::OnSyntaxHelp( wxHyperlinkEvent& aEvent )
916{
917 if( m_helpWindow )
918 {
919 m_helpWindow->ShowModeless();
920 return;
921 }
922 std::vector<wxString> msg;
923 msg.clear();
924
925 wxString t =
927 ;
928 msg.emplace_back( t );
929 t =
931 ;
932 msg.emplace_back( t );
933 t =
935 ;
936 msg.emplace_back( t );
937 t =
939 ;
940 msg.emplace_back( t );
941 t =
943 ;
944 msg.emplace_back( t );
945 t =
947 ;
948 msg.emplace_back( t );
949 t =
951 ;
952 msg.emplace_back( t );
953 t =
955 ;
956 msg.emplace_back( t );
957 t =
959 ;
960 msg.emplace_back( t );
961 t =
963 ;
964 msg.emplace_back( t );
965
966 wxString msg_txt = wxEmptyString;
967
968 for( wxString i : msg )
969 msg_txt << wxGetTranslation( i );
970
971#ifdef __WXMAC__
972 msg_txt.Replace( wxT( "Ctrl+" ), wxT( "Cmd+" ) );
973#endif
974 const wxString& msGg_txt = msg_txt;
975
976 m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Syntax Help" ) );
977 m_helpWindow->SetDialogSizeInDU( 420, 320 );
978
979 wxString html_txt = wxEmptyString;
980 ConvertMarkdown2Html( msGg_txt, html_txt );
981
982 html_txt.Replace( wxS( "<td" ), wxS( "<td valign=top" ) );
983 m_helpWindow->AddHTML_Text( html_txt );
984
985 m_helpWindow->ShowModeless();
986}
987
988
990{
991 if( !m_frame->Prj().IsNullProject() )
992 {
993 wxFileName relFile = aBoard->GetFileName();
994 relFile.SetExt( FILEEXT::DesignRulesFileExtension );
995
996 wxFileName absFile( aBoard->GetProject()->AbsolutePath( relFile.GetFullName() ) );
997
998 if( absFile.FileExists() )
999 {
1000 wxTextFile file( absFile.GetFullPath() );
1001
1002 if( file.Open() )
1003 {
1004 m_textEditor->ClearAll();
1005
1006 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
1007 {
1009 m_textEditor->AddText( str << '\n' );
1010 }
1011
1012 m_textEditor->EmptyUndoBuffer();
1013
1014 wxCommandEvent dummy;
1015 OnCompile( dummy );
1016 }
1017 }
1018 }
1019}
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
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
std::set< wxString > GetNetClassAssignmentCandidates() const
Return the set of netname candidates for netclass assignment.
Definition board.cpp:2464
const wxString & GetFileName() const
Definition board.h:359
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:728
PROJECT * GetProject() const
Definition board.h:579
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1082
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:967
APPEARANCE m_Appearance
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
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:296
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:547
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:391
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:61
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition confirm.cpp:259
This file is part of the common library.
#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
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
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
Definition of file extensions used in Kicad.