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