47#include <unordered_map>
59 [
this]( wxKeyEvent& aEvent )
62 wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
65 [
this]( wxStyledTextEvent& aEvent )
74 m_typeRegex.Compile(
"^Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
75 m_viaTypeRegex.Compile(
"^Via_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
76 m_padTypeRegex.Compile(
"^Pad_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
77 m_pinTypeRegex.Compile(
"^Pin_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
78 m_fabPropRegex.Compile(
"^Fabrication_Property\\s*[!=]=\\s*$", wxRE_ADVANCED );
79 m_shapeRegex.Compile(
"^Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
84 m_hJustRegex.Compile(
"^Horizontal_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
85 m_vJustRegex.Compile(
"^Vertical_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
89 m_textEditor->SetZoom(
Pgm().GetCommonSettings()->m_Appearance.text_editor_zoom );
111 if( aEvent.GetKeyCode() == WXK_ESCAPE && !
m_textEditor->AutoCompActive() )
115 if(
IsOK( wxGetTopLevelParent(
this ),
_(
"Cancel Changes?" ) ) )
133 menu.Append( wxID_UNDO,
_(
"Undo" ) );
134 menu.Append( wxID_REDO,
_(
"Redo" ) );
136 menu.AppendSeparator();
138 menu.Append( 1,
_(
"Cut" ) );
139 menu.Append( 2,
_(
"Copy" ) );
140 menu.Append( 3,
_(
"Paste" ) );
141 menu.Append( 4,
_(
"Delete" ) );
143 menu.AppendSeparator();
145 menu.Append( 5,
_(
"Select All" ) );
147 menu.AppendSeparator();
149 menu.Append( wxID_ZOOM_IN,
_(
"Zoom In" ) );
150 menu.Append( wxID_ZOOM_OUT,
_(
"Zoom Out" ) );
152 menu.AppendSeparator();
154 menu.Append( 6,
_(
"Show Matching Items" ) );
156 switch( GetPopupMenuSelectionFromUser( menu ) )
205 if(
m_frame->Prj().IsNullProject() )
214 const int cursorLine =
m_textEditor->LineFromPosition( cursorPos ) + 1;
215 const int cursorCol = cursorPos -
m_textEditor->PositionFromLine( cursorLine - 1 ) + 1;
217 std::vector<std::shared_ptr<DRC_RULE>> rules;
221 std::function<bool( wxString* )>
resolver =
222 [&]( wxString* token ) ->
bool
227 return m_frame->GetBoard()->ResolveTextVar( token, 0 );
230 wxString rulesText =
m_frame->GetBoard()->ConvertCrossReferencesToKIIDs(
text );
245 wxString targetRuleName;
246 bool foundRule =
false;
250 std::string utf8(
text.mb_str( wxConvUTF8 ) );
251 DRC_RULES_LEXER lex( utf8,
_(
"DRC rules" ) );
261 std::vector<RuleFrame> ruleStack;
266 int tok = lex.NextTok();
273 int leftLine = lex.CurLineNumber();
274 int leftCol = lex.CurOffset();
278 int sub = lex.NextTok();
280 if( sub == DRCRULE_T::T_rule )
283 int nameTok = lex.NextTok();
286 name = wxString::FromUTF8( lex.CurText() );
288 ruleStack.push_back( { depth, leftLine, leftCol,
name } );
305 if( !ruleStack.empty() && ruleStack.back().depth == depth )
307 RuleFrame opened = ruleStack.back();
308 ruleStack.pop_back();
310 int endLine = lex.CurLineNumber();
311 int endCol = lex.CurOffset();
313 bool afterStart = cursorLine > opened.startLine
314 || ( cursorLine == opened.startLine && cursorCol >= opened.startCol );
315 bool beforeEnd = cursorLine < endLine || ( cursorLine == endLine && cursorCol <= endCol );
317 if( afterStart && beforeEnd )
319 targetRuleName = opened.name;
331 m_errorsReport->Report( wxString::Format(
_(
"Could not scan rules text: %s" ), e.
What() ),
339 m_errorsReport->Report(
_(
"Place the cursor inside a (rule ...) block to show matching "
346 if( targetRuleName.IsEmpty() )
348 m_errorsReport->Report(
_(
"The rule under the cursor has no name; add a name to identify "
355 std::shared_ptr<DRC_RULE> targetRule;
357 for(
const auto& rule : rules )
359 if( rule->m_Name == targetRuleName )
368 m_errorsReport->Report( wxString::Format(
_(
"Rule '%s' could not be located after "
376 if( targetRule->m_Condition && !targetRule->m_Condition->GetExpression().IsEmpty()
383 std::shared_ptr<DRC_ENGINE> engine =
m_frame->GetBoard()->GetDesignSettings().m_DRCEngine;
392 std::vector<BOARD_ITEM*> allMatches = engine->GetItemsMatchingRule( targetRule,
m_errorsReport );
394 std::vector<BOARD_ITEM*> matches;
398 switch( item->Type() )
403 default: matches.push_back( item );
break;
407 m_frame->FocusOnItems( matches );
410 wxString::Format(
_(
"Rule '%s': %zu matching item(s)." ), targetRule->m_Name, matches.size() ),
418 if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKey() ==
' ' )
432 for(
int line =
m_textEditor->LineFromPosition( currentPos ); line > 0; line-- )
435 wxString beginning =
m_textEditor->GetTextRange( lineStart, lineStart + 10 );
437 if( beginning.StartsWith( wxT(
"(rule " ) ) )
439 startPos = lineStart;
454 auto isDisallowToken =
455 [](
const wxString& token ) ->
bool
457 return token == wxT(
"blind_via" )
458 || token == wxT(
"buried_via" )
459 || token == wxT(
"graphic" )
460 || token == wxT(
"hole" )
461 || token == wxT(
"micro_via" )
462 || token == wxT(
"pad" )
463 || token == wxT(
"text" )
464 || token == wxT(
"through_via" )
465 || token == wxT(
"track" )
466 || token == wxT(
"via" )
467 || token == wxT(
"zone" );
470 auto isConstraintTypeToken =
471 [](
const wxString& token ) ->
bool
473 return token == wxT(
"annular_width" )
474 || token == wxT(
"assertion" )
475 || token == wxT(
"clearance" )
476 || token == wxT(
"connection_width" )
477 || token == wxT(
"courtyard_clearance" )
478 || token == wxT(
"diff_pair_gap" )
479 || token == wxT(
"diff_pair_uncoupled" )
480 || token == wxT(
"disallow" )
481 || token == wxT(
"edge_clearance" )
482 || token == wxT(
"length" )
483 || token == wxT(
"hole_clearance" )
484 || token == wxT(
"hole_size" )
485 || token == wxT(
"hole_to_hole" )
486 || token == wxT(
"min_resolved_spokes" )
487 || token == wxT(
"physical_clearance" )
488 || token == wxT(
"physical_hole_clearance" )
489 || token == wxT(
"silk_clearance" )
490 || token == wxT(
"skew" )
491 || token == wxT(
"solder_mask_expansion" )
492 || token == wxT(
"solder_paste_abs_margin" )
493 || token == wxT(
"solder_paste_rel_margin" )
494 || token == wxT(
"text_height" )
495 || token == wxT(
"text_thickness" )
496 || token == wxT(
"thermal_relief_gap" )
497 || token == wxT(
"thermal_spoke_width" )
498 || token == wxT(
"track_width" )
499 || token == wxT(
"track_angle" )
500 || token == wxT(
"track_segment_length" )
501 || token == wxT(
"via_count" )
502 || token == wxT(
"via_diameter" )
503 || token == wxT(
"zone_connection" );
506 std::stack<wxString> sexprs;
509 wxString constraintType;
510 int context = NO_CONTEXT;
511 int expr_context = NO_CONTEXT;
513 for(
int i = startPos; i < currentPos; ++i )
521 else if( context ==
STRING )
525 context = NO_CONTEXT;
529 if( expr_context ==
STRING )
532 expr_context = NO_CONTEXT;
539 partial = wxEmptyString;
544 partial = wxEmptyString;
545 expr_context = STRUCT_REF;
556 partial = wxEmptyString;
561 if( context == SEXPR_OPEN && !partial.IsEmpty() )
564 sexprs.push( partial );
567 partial = wxEmptyString;
568 context = SEXPR_OPEN;
572 while( !sexprs.empty() && ( sexprs.top() == wxT(
"assertion" )
573 || sexprs.top() == wxT(
"disallow" )
574 || isDisallowToken( sexprs.top() )
575 || sexprs.top() == wxT(
"min_resolved_spokes" )
576 || sexprs.top() == wxT(
"zone_connection" ) ) )
581 if( !sexprs.empty() )
584 if( partial == wxT(
"within_diff_pairs" ) )
586 partial = wxEmptyString;
590 if( sexprs.top() == wxT(
"constraint" ) )
592 constraintType = wxEmptyString;
599 context = NO_CONTEXT;
603 if( context == SEXPR_OPEN && !partial.IsEmpty() )
606 sexprs.push( partial );
608 if( partial == wxT(
"constraint" )
609 || partial == wxT(
"layer" )
610 || partial == wxT(
"severity" ) )
612 context = SEXPR_TOKEN;
614 else if( partial == wxT(
"rule" )
615 || partial == wxT(
"condition" ) )
617 context = SEXPR_STRING;
621 context = NO_CONTEXT;
624 partial = wxEmptyString;
627 else if( partial == wxT(
"disallow" )
628 || isDisallowToken( partial )
629 || partial == wxT(
"min_resolved_spokes" )
630 || partial == wxT(
"zone_connection" ) )
633 sexprs.push( partial );
635 partial = wxEmptyString;
636 context = SEXPR_TOKEN;
639 else if( partial == wxT(
"assertion" ) )
642 sexprs.push( partial );
644 partial = wxEmptyString;
645 context = SEXPR_STRING;
648 else if( isConstraintTypeToken( partial ) )
650 constraintType = partial;
653 context = NO_CONTEXT;
663 if( context == SEXPR_OPEN )
667 tokens = wxT(
"rule|"
670 else if( sexprs.top() == wxT(
"rule" ) )
672 tokens = wxT(
"condition|"
677 else if( sexprs.top() == wxT(
"constraint" ) )
679 if( constraintType == wxT(
"skew" ) )
680 tokens = wxT(
"max|min|opt|within_diff_pairs" );
682 tokens = wxT(
"max|min|opt" );
685 else if( context == SEXPR_TOKEN )
691 else if( sexprs.top() == wxT(
"constraint" ) )
693 tokens = wxT(
"annular_width|"
698 "courtyard_clearance|"
701 "diff_pair_uncoupled|"
708 "min_resolved_spokes|"
709 "physical_clearance|"
710 "physical_hole_clearance|"
713 "solder_mask_expansion|"
714 "solder_paste_abs_margin|"
715 "solder_paste_rel_margin|"
718 "thermal_relief_gap|"
719 "thermal_spoke_width|"
722 "track_segment_length|"
727 else if( sexprs.top() == wxT(
"disallow" ) || isDisallowToken( sexprs.top() ) )
729 tokens = wxT(
"blind_via|"
742 else if( sexprs.top() == wxT(
"zone_connection" ) )
744 tokens = wxT(
"none|solid|thermal_reliefs" );
746 else if( sexprs.top() == wxT(
"min_resolved_spokes" ) )
748 tokens = wxT(
"0|1|2|3|4" );
750 else if( sexprs.top() == wxT(
"layer" ) )
752 tokens = wxT(
"inner|outer|\"x\"" );
754 else if( sexprs.top() == wxT(
"severity" ) )
756 tokens = wxT(
"warning|error|ignore|exclusion" );
759 else if( context == SEXPR_STRING && !sexprs.empty()
760 && ( sexprs.top() == wxT(
"condition" ) || sexprs.top() == wxT(
"assertion" ) ) )
764 else if( context ==
STRING && !sexprs.empty()
765 && ( sexprs.top() == wxT(
"condition" ) || sexprs.top() == wxT(
"assertion" ) ) )
767 if( expr_context == STRUCT_REF )
770 std::set<wxString> propNames;
774 const std::vector<PROPERTY_BASE*>& props = propMgr.
GetProperties( cls.type );
781 if( prop->IsHiddenFromRulesEditor() )
784 wxString ref( prop->Name() );
785 ref.Replace( wxT(
" " ), wxT(
"_" ) );
786 propNames.insert( ref );
790 for(
const wxString& propName : propNames )
791 tokens += wxT(
"|" ) + propName;
797 if( !funcSig.Contains(
"DEPRECATED" ) )
798 tokens += wxT(
"|" ) + funcSig;
801 else if( expr_context ==
STRING )
806 std::shared_ptr<NET_SETTINGS>& netSettings = bds.
m_NetSettings;
808 for(
const auto& [
name, netclass] : netSettings->GetNetclasses() )
809 tokens += wxT(
"|" ) +
name;
816 tokens += wxT(
"|" ) + netnameCandidate;
820 tokens = wxT(
"Bitmap|"
836 tokens = wxT(
"Through|"
843 tokens = wxT(
"Through-hole|"
846 "NPTH, mechanical" );
850 tokens = wxT(
"Input|"
865 tokens = wxT(
"None|"
867 "Fiducial, global to board|"
868 "Fiducial, local to footprint|"
875 tokens = wxT(
"Segment|"
884 tokens = wxT(
"Circle|"
889 "Chamfered rectangle|"
894 tokens = wxT(
"Inherited|"
898 "Thermal reliefs for PTH" );
902 tokens = wxT(
"Inherited|"
909 tokens = wxT(
"Default|"
918 tokens = wxT(
"Left|"
931 if( !tokens.IsEmpty() )
942 std::vector<std::shared_ptr<DRC_RULE>> dummyRules;
944 std::function<bool( wxString* )>
resolver =
945 [&]( wxString* token ) ->
bool
950 return m_frame->GetBoard()->ResolveTextVar( token, 0 );
954 rulesText =
m_frame->GetBoard()->ConvertCrossReferencesToKIIDs( rulesText );
964 m_errorsReport->Report( wxString::Format( wxT(
"%s <a href='%d:%d'>%s</a>%s" ),
984 std::map<std::pair<wxString, wxString>, wxString> seenConditions;
985 std::regex netclassPattern(
"NetClass\\s*[!=]=\\s*'\"?([^\"\\s]+)'\"?" );
987 for(
const auto& rule : aRules )
991 if( rule->m_Condition )
992 condition = rule->m_Condition->GetExpression();
994 condition.Trim(
true ).Trim(
false );
996 auto key = std::make_pair( condition, rule->m_LayerSource );
998 if( seenConditions.count( key ) )
1000 m_errorsReport->Report( wxString::Format(
_(
"Rules '%s' and '%s' share the same condition." ),
1002 seenConditions[key] ),
1007 seenConditions[key] = rule->m_Name;
1010 std::string condUtf8 = condition.ToStdString();
1011 std::sregex_iterator it( condUtf8.begin(), condUtf8.end(), netclassPattern );
1012 std::sregex_iterator
end;
1014 for( ; it !=
end; ++it )
1016 wxString ncName = wxString::FromUTF8( ( *it )[1].str() );
1020 m_errorsReport->Report( wxString::Format(
_(
"Rule '%s' references undefined netclass '%s'." ),
1027 const bool isInner = rule->m_LayerSource.IsSameAs( wxT(
"'inner'" ),
false );
1028 const bool isOuter = rule->m_LayerSource.IsSameAs( wxT(
"'outer'" ),
false );
1030 if( !rule->m_LayerSource.IsEmpty() && !isInner && !isOuter )
1032 LSET invalid = rule->m_LayerCondition & ~enabledLayers;
1040 if( !badLayers.IsEmpty() )
1046 m_errorsReport->Report( wxString::Format(
_(
"Rule '%s' references undefined layer(s): %s." ),
1058 wxString link =
event.GetLinkInfo().GetHref();
1059 wxArrayString parts;
1060 long line = 0, offset = 0;
1064 if( parts.size() > 1 )
1066 parts[0].ToLong( &line );
1067 parts[1].ToLong( &offset );
1070 int pos =
m_textEditor->PositionFromLine( line - 1 ) + ( offset - 1 );
1083 wxFileName rulesFile(
m_frame->GetBoard()->GetDesignRulesPath() );
1085 if( rulesFile.FileExists() )
1087 wxTextFile file( rulesFile.GetFullPath() );
1091 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
1099 wxCommandEvent
dummy;
1110 if(
m_frame->Prj().IsNullProject() )
1113 m_textEditor->AddText(
_(
"Design rules cannot be added without a project" ) );
1126 if(
m_frame->Prj().IsNullProject() )
1132 wxString rulesFilepath =
m_frame->GetBoard()->GetDesignRulesPath();
1135 std::string utf8 = std::string( content.mb_str( wxConvUTF8 ) );
1136 wxString writeError;
1140 wxLogError(
_(
"Cannot save design rules to '%s': %s" ), rulesFilepath, writeError );
1146 m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine( rulesFilepath );
1165 std::vector<wxString> msg;
1171 msg.emplace_back( t );
1175 msg.emplace_back( t );
1179 msg.emplace_back( t );
1183 msg.emplace_back( t );
1187 msg.emplace_back( t );
1191 msg.emplace_back( t );
1195 msg.emplace_back( t );
1199 msg.emplace_back( t );
1203 msg.emplace_back( t );
1207 msg.emplace_back( t );
1209 wxString msg_txt = wxEmptyString;
1211 for( wxString i : msg )
1212 msg_txt << wxGetTranslation( i );
1215 msg_txt.Replace( wxT(
"Ctrl+" ), wxT(
"Cmd+" ) );
1217 const wxString& msGg_txt = msg_txt;
1222 wxString html_txt = wxEmptyString;
1225 html_txt.Replace( wxS(
"<td" ), wxS(
"<td valign=top" ) );
1234 if( !
m_frame->Prj().IsNullProject() )
1241 if( absFile.FileExists() )
1243 wxTextFile file( absFile.GetFullPath() );
1249 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
1257 wxCommandEvent
dummy;
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Information pertinent to a Pcbnew printed circuit board.
std::set< wxString > GetNetClassAssignmentCandidates() const
Return the set of netname candidates for netclass assignment.
const wxString & GetFileName() const
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
PROJECT * GetProject() const
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
static bool IsSymbol(int aTok)
Test a token to see if it is a symbol.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
LSET is a set of PCB_LAYER_IDs.
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
bool HasNetclass(const wxString &netclassName) const
Determines if the given netclass exists.
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)
void OnContextMenu(wxMouseEvent &event) override
~PANEL_SETUP_RULES() override
HTML_MESSAGE_BOX * m_helpWindow
void OnCompile(wxCommandEvent &event) override
wxRegEx m_padConnectionsRegex
wxRegEx m_zoneConnStyleRegex
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
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.
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)
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
This file is part of the common library.
bool IsComponentClassSelector(const wxString &aToken)
${Class:X} inside a DRC rule is a component-class selector consumed by testFootprintSelector(),...
static FILENAME_RESOLVER * resolver
static const std::string DesignRulesFileExtension
PCB_LAYER_ID
A quick note on layer IDs:
PGM_BASE & Pgm()
The global program "get" accessor.
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
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
@ PCB_NETINFO_T
class NETINFO_ITEM, a description of a net
Definition of file extensions used in Kicad.