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