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( "blind_via|"
513 "buried_via|"
514 "footprint|"
515 "graphic|"
516 "hole|"
517 "micro_via|"
518 "pad|"
519 "text|"
520 "through_via|"
521 "track|"
522 "via|"
523 "zone" );
524 }
525 else if( sexprs.top() == wxT( "zone_connection" ) )
526 {
527 tokens = wxT( "none|solid|thermal_reliefs" );
528 }
529 else if( sexprs.top() == wxT( "min_resolved_spokes" ) )
530 {
531 tokens = wxT( "0|1|2|3|4" );
532 }
533 else if( sexprs.top() == wxT( "layer" ) )
534 {
535 tokens = wxT( "inner|outer|\"x\"" );
536 }
537 else if( sexprs.top() == wxT( "severity" ) )
538 {
539 tokens = wxT( "warning|error|ignore|exclusion" );
540 }
541 }
542 else if( context == SEXPR_STRING && !sexprs.empty()
543 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
544 {
545 m_textEditor->AddText( wxT( "\"" ) );
546 }
547 else if( context == STRING && !sexprs.empty()
548 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
549 {
550 if( expr_context == STRUCT_REF )
551 {
553 std::set<wxString> propNames;
554
555 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
556 {
557 const std::vector<PROPERTY_BASE*>& props = propMgr.GetProperties( cls.type );
558
559 for( PROPERTY_BASE* prop : props )
560 {
561 // TODO: It would be nice to replace IsHiddenFromRulesEditor with a nickname
562 // system, so that two different properies don't need to be created. This is
563 // a bigger change than I want to make right now, though.
564 if( prop->IsHiddenFromRulesEditor() )
565 continue;
566
567 wxString ref( prop->Name() );
568 ref.Replace( wxT( " " ), wxT( "_" ) );
569 propNames.insert( ref );
570 }
571 }
572
573 for( const wxString& propName : propNames )
574 tokens += wxT( "|" ) + propName;
575
577
578 for( const wxString& funcSig : functions.GetSignatures() )
579 {
580 if( !funcSig.Contains( "DEPRECATED" ) )
581 tokens += wxT( "|" ) + funcSig;
582 }
583 }
584 else if( expr_context == STRING )
585 {
586 if( m_netClassRegex.Matches( last ) )
587 {
588 BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
589 std::shared_ptr<NET_SETTINGS>& netSettings = bds.m_NetSettings;
590
591 for( const auto& [name, netclass] : netSettings->GetNetclasses() )
592 tokens += wxT( "|" ) + name;
593 }
594 else if( m_netNameRegex.Matches( last ) )
595 {
596 BOARD* board = m_frame->GetBoard();
597
598 for( const wxString& netnameCandidate : board->GetNetClassAssignmentCandidates() )
599 tokens += wxT( "|" ) + netnameCandidate;
600 }
601 else if( m_typeRegex.Matches( last ) )
602 {
603 tokens = wxT( "Bitmap|"
604 "Dimension|"
605 "Footprint|"
606 "Graphic|"
607 "Group|"
608 "Leader|"
609 "Pad|"
610 "Target|"
611 "Text|"
612 "Text Box|"
613 "Track|"
614 "Via|"
615 "Zone" );
616 }
617 else if( m_viaTypeRegex.Matches( last ) )
618 {
619 tokens = wxT( "Through|"
620 "Blind|"
621 "Buried|"
622 "Micro" );
623 }
624 else if( m_padTypeRegex.Matches( last ) )
625 {
626 tokens = wxT( "Through-hole|"
627 "SMD|"
628 "Edge connector|"
629 "NPTH, mechanical" );
630 }
631 else if( m_pinTypeRegex.Matches( last ) )
632 {
633 tokens = wxT( "Input|"
634 "Output|"
635 "Bidirectional|"
636 "Tri-state|"
637 "Passive|"
638 "Free|"
639 "Unspecified|"
640 "Power input|"
641 "Power output|"
642 "Open collector|"
643 "Open emitter|"
644 "Unconnected" );
645 }
646 else if( m_fabPropRegex.Matches( last ) )
647 {
648 tokens = wxT( "None|"
649 "BGA pad|"
650 "Fiducial, global to board|"
651 "Fiducial, local to footprint|"
652 "Test point pad|"
653 "Heatsink pad|"
654 "Castellated pad" );
655 }
656 else if( m_shapeRegex.Matches( last ) )
657 {
658 tokens = wxT( "Segment|"
659 "Rectangle|"
660 "Arc|"
661 "Circle|"
662 "Polygon|"
663 "Bezier" );
664 }
665 else if( m_padShapeRegex.Matches( last ) )
666 {
667 tokens = wxT( "Circle|"
668 "Rectangle|"
669 "Oval|"
670 "Trapezoid|"
671 "Rounded rectangle|"
672 "Chamfered rectangle|"
673 "Custom" );
674 }
675 else if( m_padConnectionsRegex.Matches( last ) )
676 {
677 tokens = wxT( "Inherited|"
678 "None|"
679 "Solid|"
680 "Thermal reliefs|"
681 "Thermal reliefs for PTH" );
682 }
683 else if( m_zoneConnStyleRegex.Matches( last ) )
684 {
685 tokens = wxT( "Inherited|"
686 "None|"
687 "Solid|"
688 "Thermal reliefs" );
689 }
690 else if( m_lineStyleRegex.Matches( last ) )
691 {
692 tokens = wxT( "Default|"
693 "Solid|"
694 "Dashed|"
695 "Dotted|"
696 "Dash-Dot|"
697 "Dash-Dot-Dot" );
698 }
699 else if( m_hJustRegex.Matches( last ) )
700 {
701 tokens = wxT( "Left|"
702 "Center|"
703 "Right" );
704 }
705 else if( m_vJustRegex.Matches( last ) )
706 {
707 tokens = wxT( "Top|"
708 "Center|"
709 "Bottom" );
710 }
711 }
712 }
713
714 if( !tokens.IsEmpty() )
715 m_scintillaTricks->DoAutocomplete( partial, wxSplit( tokens, '|' ) );
716}
717
718
719void PANEL_SETUP_RULES::OnCompile( wxCommandEvent& event )
720{
721 m_errorsReport->Clear();
722
723 try
724 {
725 std::vector<std::shared_ptr<DRC_RULE>> dummyRules;
726
727 std::function<bool( wxString* )> resolver =
728 [&]( wxString* token ) -> bool
729 {
730 if( m_frame->GetBoard()->ResolveTextVar( token, 0 ) )
731 return true;
732
733 return false;
734 };
735
736 wxString rulesText = m_textEditor->GetText();
737 rulesText = m_frame->GetBoard()->ConvertCrossReferencesToKIIDs( rulesText );
738 rulesText = ExpandTextVars( rulesText, &resolver );
739
740 DRC_RULES_PARSER parser( rulesText, _( "DRC rules" ) );
741
742 parser.Parse( dummyRules, m_errorsReport );
743 checkPlausibility( dummyRules );
744 }
745 catch( PARSE_ERROR& pe )
746 {
747 m_errorsReport->Report( wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>%s" ),
748 _( "ERROR:" ),
749 pe.lineNumber,
750 pe.byteIndex,
751 pe.ParseProblem(),
752 wxEmptyString ),
754 }
755
756 m_errorsReport->Flush();
757}
758
759
760void PANEL_SETUP_RULES::checkPlausibility( const std::vector<std::shared_ptr<DRC_RULE>>& aRules )
761{
762 BOARD* board = m_frame->GetBoard();
764 LSET enabledLayers = board->GetEnabledLayers();
765
766 // Key by (condition, layerSource) so rules with different layer scopes are considered distinct
767 std::map<std::pair<wxString, wxString>, wxString> seenConditions;
768 std::regex netclassPattern( "NetClass\\s*[!=]=\\s*'\"?([^\"\\s]+)'\"?" );
769
770 for( const auto& rule : aRules )
771 {
772 wxString condition;
773
774 if( rule->m_Condition )
775 condition = rule->m_Condition->GetExpression();
776
777 condition.Trim( true ).Trim( false );
778
779 auto key = std::make_pair( condition, rule->m_LayerSource );
780
781 if( seenConditions.count( key ) )
782 {
783 m_errorsReport->Report( wxString::Format( _( "Rules '%s' and '%s' share the same condition." ),
784 rule->m_Name,
785 seenConditions[key] ),
787 }
788 else
789 {
790 seenConditions[key] = rule->m_Name;
791 }
792
793 std::string condUtf8 = condition.ToStdString();
794 std::sregex_iterator it( condUtf8.begin(), condUtf8.end(), netclassPattern );
795 std::sregex_iterator end;
796
797 for( ; it != end; ++it )
798 {
799 wxString ncName = wxString::FromUTF8( ( *it )[1].str() );
800
801 if( !bds.m_NetSettings->HasNetclass( ncName ) )
802 {
803 m_errorsReport->Report( wxString::Format( _( "Rule '%s' references undefined netclass '%s'." ),
804 rule->m_Name,
805 ncName ),
807 }
808 }
809
810 const bool isInner = rule->m_LayerSource.IsSameAs( wxT( "'inner'" ), false );
811 const bool isOuter = rule->m_LayerSource.IsSameAs( wxT( "'outer'" ), false );
812
813 if( !rule->m_LayerSource.IsEmpty() && !isInner && !isOuter )
814 {
815 LSET invalid = rule->m_LayerCondition & ~enabledLayers;
816
817 if( invalid.any() )
818 {
819 wxString badLayers;
820
821 for( PCB_LAYER_ID layer : invalid.Seq() )
822 {
823 if( !badLayers.IsEmpty() )
824 badLayers += ", ";
825
826 badLayers += board->GetLayerName( layer );
827 }
828
829 m_errorsReport->Report( wxString::Format( _( "Rule '%s' references undefined layer(s): %s." ),
830 rule->m_Name,
831 badLayers ),
833 }
834 }
835 }
836}
837
838
839void PANEL_SETUP_RULES::OnErrorLinkClicked( wxHtmlLinkEvent& event )
840{
841 wxString link = event.GetLinkInfo().GetHref();
842 wxArrayString parts;
843 long line = 0, offset = 0;
844
845 wxStringSplit( link, parts, ':' );
846
847 if( parts.size() > 1 )
848 {
849 parts[0].ToLong( &line );
850 parts[1].ToLong( &offset );
851 }
852
853 int pos = m_textEditor->PositionFromLine( line - 1 ) + ( offset - 1 );
854
855 m_textEditor->GotoPos( pos );
856
857 m_textEditor->SetFocus();
858}
859
860
862{
863 wxFileName rulesFile( m_frame->GetDesignRulesPath() );
864
865 if( rulesFile.FileExists() )
866 {
867 wxTextFile file( rulesFile.GetFullPath() );
868
869 if( file.Open() )
870 {
871 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
872 {
874 m_textEditor->AddText( str << '\n' );
875 }
876
877 m_textEditor->EmptyUndoBuffer();
878
879 wxCommandEvent dummy;
880 OnCompile( dummy );
881 }
882 }
883 else
884 {
885 m_textEditor->AddText( wxT( "(version 1)\n" ) );
886 }
887
888 m_originalText = m_textEditor->GetText();
889
890 if( m_frame->Prj().IsNullProject() )
891 {
892 m_textEditor->ClearAll();
893 m_textEditor->AddText( _( "Design rules cannot be added without a project" ) );
894 m_textEditor->Disable();
895 }
896
897 return true;
898}
899
900
902{
903 if( m_originalText == m_textEditor->GetText() )
904 return true;
905
906 if( m_frame->Prj().IsNullProject() )
907 return true;
908
909 wxString rulesFilepath = m_frame->GetDesignRulesPath();
910
911 wxString content = m_textEditor->GetText();
912 std::string utf8 = std::string( content.mb_str( wxConvUTF8 ) );
913 wxString writeError;
914
915 if( !KIPLATFORM::IO::AtomicWriteFile( rulesFilepath, utf8.data(), utf8.size(), &writeError ) )
916 {
917 wxLogError( _( "Cannot save design rules to '%s': %s" ), rulesFilepath, writeError );
918 return false;
919 }
920
921 try
922 {
923 m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine( rulesFilepath );
924 return true;
925 }
926 catch( PARSE_ERROR& )
927 {
928 // Don't lock them in to the Setup dialog if they have bad rules. They've already
929 // saved them so we can allow an exit.
930 return true;
931 }
932}
933
934
935void PANEL_SETUP_RULES::OnSyntaxHelp( wxHyperlinkEvent& aEvent )
936{
937 if( m_helpWindow )
938 {
939 m_helpWindow->ShowModeless();
940 return;
941 }
942 std::vector<wxString> msg;
943 msg.clear();
944
945 wxString 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 t =
979 ;
980 msg.emplace_back( t );
981 t =
983 ;
984 msg.emplace_back( t );
985
986 wxString msg_txt = wxEmptyString;
987
988 for( wxString i : msg )
989 msg_txt << wxGetTranslation( i );
990
991#ifdef __WXMAC__
992 msg_txt.Replace( wxT( "Ctrl+" ), wxT( "Cmd+" ) );
993#endif
994 const wxString& msGg_txt = msg_txt;
995
996 m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Syntax Help" ) );
997 m_helpWindow->SetDialogSizeInDU( 420, 320 );
998
999 wxString html_txt = wxEmptyString;
1000 ConvertMarkdown2Html( msGg_txt, html_txt );
1001
1002 html_txt.Replace( wxS( "<td" ), wxS( "<td valign=top" ) );
1003 m_helpWindow->AddHTML_Text( html_txt );
1004
1005 m_helpWindow->ShowModeless();
1006}
1007
1008
1010{
1011 if( !m_frame->Prj().IsNullProject() )
1012 {
1013 wxFileName relFile = aBoard->GetFileName();
1014 relFile.SetExt( FILEEXT::DesignRulesFileExtension );
1015
1016 wxFileName absFile( aBoard->GetProject()->AbsolutePath( relFile.GetFullName() ) );
1017
1018 if( absFile.FileExists() )
1019 {
1020 wxTextFile file( absFile.GetFullPath() );
1021
1022 if( file.Open() )
1023 {
1024 m_textEditor->ClearAll();
1025
1026 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
1027 {
1029 m_textEditor->AddText( str << '\n' );
1030 }
1031
1032 m_textEditor->EmptyUndoBuffer();
1033
1034 wxCommandEvent dummy;
1035 OnCompile( dummy );
1036 }
1037 }
1038 }
1039}
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:2744
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.