KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_re_rule_loader.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 (C) 2024 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 "drc_re_rule_loader.h"
25
26#include <reporter.h>
28#include <drc/drc_rule_parser.h>
30#include <wx/ffile.h>
31
44
45
49
50
51double DRC_RULE_LOADER::toMM( int aValue )
52{
53 return aValue / 1000000.0;
54}
55
56
58{
59 for( const DRC_CONSTRAINT& constraint : aRule.m_Constraints )
60 {
61 if( constraint.m_Type == aType )
62 return &constraint;
63 }
64
65 return nullptr;
66}
67
68
69std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA>
71 const DRC_RULE& aRule,
72 const std::set<DRC_CONSTRAINT_T>& aClaimedConstraints )
73{
74 switch( aPanel )
75 {
76 case VIA_STYLE:
77 {
78 auto data = std::make_shared<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>();
79 data->SetRuleName( aRule.m_Name );
80 data->SetConstraintCode( "via_style" );
81
83 const DRC_CONSTRAINT* holeSize = findConstraint( aRule, HOLE_SIZE_CONSTRAINT );
84
85 if( viaDia )
86 {
87 data->SetMinViaDiameter( toMM( viaDia->GetValue().Min() ) );
88 data->SetPreferredViaDiameter( toMM( viaDia->GetValue().Opt() ) );
89 data->SetMaxViaDiameter( toMM( viaDia->GetValue().Max() ) );
90 }
91
92 if( holeSize )
93 {
94 data->SetMinViaHoleSize( toMM( holeSize->GetValue().Min() ) );
95 data->SetPreferredViaHoleSize( toMM( holeSize->GetValue().Opt() ) );
96 data->SetMaxViaHoleSize( toMM( holeSize->GetValue().Max() ) );
97 }
98
99 return data;
100 }
101
103 {
104 auto data = std::make_shared<DRC_RE_ROUTING_DIFF_PAIR_CONSTRAINT_DATA>();
105 data->SetRuleName( aRule.m_Name );
106 data->SetConstraintCode( "diff_pair_gap" );
107
108 const DRC_CONSTRAINT* trackWidth = findConstraint( aRule, TRACK_WIDTH_CONSTRAINT );
109 const DRC_CONSTRAINT* diffGap = findConstraint( aRule, DIFF_PAIR_GAP_CONSTRAINT );
110 const DRC_CONSTRAINT* uncoupled = findConstraint( aRule, MAX_UNCOUPLED_CONSTRAINT );
111
112 if( trackWidth )
113 {
114 data->SetMinWidth( toMM( trackWidth->GetValue().Min() ) );
115 data->SetPreferredWidth( toMM( trackWidth->GetValue().Opt() ) );
116 data->SetMaxWidth( toMM( trackWidth->GetValue().Max() ) );
117 }
118
119 if( diffGap )
120 {
121 data->SetMinGap( toMM( diffGap->GetValue().Min() ) );
122 data->SetPreferredGap( toMM( diffGap->GetValue().Opt() ) );
123 data->SetMaxGap( toMM( diffGap->GetValue().Max() ) );
124 }
125
126 if( uncoupled )
127 {
128 data->SetMaxUncoupledLength( toMM( uncoupled->GetValue().Max() ) );
129 }
130
131 const DRC_CONSTRAINT* skew = findConstraint( aRule, SKEW_CONSTRAINT );
132
133 if( skew )
134 {
135 data->SetMaxSkew( toMM( skew->GetValue().Max() ) );
136 }
137
138 return data;
139 }
140
142 {
143 auto data = std::make_shared<DRC_RE_MINIMUM_TEXT_HEIGHT_THICKNESS_CONSTRAINT_DATA>();
144 data->SetRuleName( aRule.m_Name );
145 data->SetConstraintCode( "text_height" );
146
147 const DRC_CONSTRAINT* textHeight = findConstraint( aRule, TEXT_HEIGHT_CONSTRAINT );
148 const DRC_CONSTRAINT* textThickness = findConstraint( aRule, TEXT_THICKNESS_CONSTRAINT );
149
150 if( textHeight )
151 data->SetMinTextHeight( toMM( textHeight->GetValue().Min() ) );
152
153 if( textThickness )
154 data->SetMinTextThickness( toMM( textThickness->GetValue().Min() ) );
155
156 return data;
157 }
158
159 case ROUTING_WIDTH:
160 {
161 auto data = std::make_shared<DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA>();
162 data->SetRuleName( aRule.m_Name );
163 data->SetConstraintCode( "track_width" );
164
165 const DRC_CONSTRAINT* trackWidth = findConstraint( aRule, TRACK_WIDTH_CONSTRAINT );
166
167 if( trackWidth )
168 {
169 data->SetMinRoutingWidth( toMM( trackWidth->GetValue().Min() ) );
170 data->SetPreferredRoutingWidth( toMM( trackWidth->GetValue().Opt() ) );
171 data->SetMaxRoutingWidth( toMM( trackWidth->GetValue().Max() ) );
172 }
173
174 return data;
175 }
176
177 case ABSOLUTE_LENGTH:
178 {
179 auto data = std::make_shared<DRC_RE_ABSOLUTE_LENGTH_TWO_CONSTRAINT_DATA>();
180 data->SetRuleName( aRule.m_Name );
181 data->SetConstraintCode( "length" );
182
183 const DRC_CONSTRAINT* length = findConstraint( aRule, LENGTH_CONSTRAINT );
184
185 if( length )
186 {
187 data->SetMinimumLength( toMM( length->GetValue().Min() ) );
188 data->SetOptimumLength( toMM( length->GetValue().Opt() ) );
189 data->SetMaximumLength( toMM( length->GetValue().Max() ) );
190 }
191
192 return data;
193 }
194
196 {
197 auto data = std::make_shared<DRC_RE_MATCHED_LENGTH_DIFF_PAIR_CONSTRAINT_DATA>();
198 data->SetRuleName( aRule.m_Name );
199 data->SetConstraintCode( "length" );
200
201 const DRC_CONSTRAINT* length = findConstraint( aRule, LENGTH_CONSTRAINT );
202
203 if( length )
204 {
205 data->SetMinimumLength( toMM( length->GetValue().Min() ) );
206 data->SetOptimumLength( toMM( length->GetValue().Opt() ) );
207 data->SetMaximumLength( toMM( length->GetValue().Max() ) );
208 }
209
210 const DRC_CONSTRAINT* skew = findConstraint( aRule, SKEW_CONSTRAINT );
211
212 if( skew )
213 data->SetMaxSkew( toMM( skew->GetValue().Max() ) );
214
215 return data;
216 }
217
218 case PERMITTED_LAYERS:
219 {
220 auto data = std::make_shared<DRC_RE_PERMITTED_LAYERS_CONSTRAINT_DATA>();
221 data->SetRuleName( aRule.m_Name );
222
223 const DRC_CONSTRAINT* constraint = findConstraint( aRule, ASSERTION_CONSTRAINT );
224
225 if( constraint && constraint->m_Test )
226 {
227 wxString expr = constraint->m_Test->GetExpression();
228
229 data->SetTopLayerEnabled( expr.Contains( wxS( "F.Cu" ) ) );
230 data->SetBottomLayerEnabled( expr.Contains( wxS( "B.Cu" ) ) );
231 }
232
233 return data;
234 }
235
237 {
238 auto data = std::make_shared<DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA>();
239 data->SetRuleName( aRule.m_Name );
240 data->SetConstraintCode( wxS( "allowed_orientation" ) );
241
242 const DRC_CONSTRAINT* constraint = findConstraint( aRule, ASSERTION_CONSTRAINT );
243
244 if( constraint && constraint->m_Test )
245 {
246 wxString expr = constraint->m_Test->GetExpression();
247
248 data->SetIsZeroDegreesAllowed( expr.Contains( wxS( "== 0 deg" ) ) );
249 data->SetIsNinetyDegreesAllowed( expr.Contains( wxS( "== 90 deg" ) ) );
250 data->SetIsOneEightyDegreesAllowed( expr.Contains( wxS( "== 180 deg" ) ) );
251 data->SetIsTwoSeventyDegreesAllowed( expr.Contains( wxS( "== 270 deg" ) ) );
252
253 if( !data->GetIsZeroDegreesAllowed() && !data->GetIsNinetyDegreesAllowed()
254 && !data->GetIsOneEightyDegreesAllowed() && !data->GetIsTwoSeventyDegreesAllowed() )
255 {
256 data->SetIsAllDegreesAllowed( true );
257 }
258 }
259 else
260 {
261 data->SetIsAllDegreesAllowed( true );
262 }
263
264 return data;
265 }
266
267 case VIAS_UNDER_SMD:
268 {
269 auto data = std::make_shared<DRC_RE_BOOL_INPUT_CONSTRAINT_DATA>();
270 data->SetRuleName( aRule.m_Name );
271 data->SetConstraintCode( wxS( "disallow via" ) );
272
273 const DRC_CONSTRAINT* constraint = findConstraint( aRule, DISALLOW_CONSTRAINT );
274
275 if( constraint )
276 {
279 data->SetBoolInputValue( ( constraint->m_DisallowFlags & viaFlags ) != 0 );
280 }
281
282 return data;
283 }
284
285 case CUSTOM_RULE:
286 {
287 auto data = std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>();
288 data->SetRuleName( aRule.m_Name );
289 return data;
290 }
291
292 default:
293 {
294 // For numeric input types, create a generic numeric constraint data
296 {
298 data->SetRuleName( aRule.m_Name );
299
300 wxString code = DRC_RULE_EDITOR_UTILS::GetConstraintCode( aPanel );
301 data->SetConstraintCode( code );
302
303 // Find the first matching constraint from the claimed set
304 for( DRC_CONSTRAINT_T type : aClaimedConstraints )
305 {
306 const DRC_CONSTRAINT* constraint = findConstraint( aRule, type );
307
308 if( constraint )
309 {
310 if( type == VIA_COUNT_CONSTRAINT )
311 data->SetNumericInputValue( toMM( constraint->GetValue().Max() ) );
312 else if( type == MIN_RESOLVED_SPOKES_CONSTRAINT )
313 data->SetNumericInputValue( constraint->GetValue().Min() );
314 else
315 data->SetNumericInputValue( toMM( constraint->GetValue().Min() ) );
316
317 break;
318 }
319 }
320
321 return data;
322 }
323
324 // Fallback to custom rule
325 auto data = std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>();
326 data->SetRuleName( aRule.m_Name );
327 return data;
328 }
329 }
330}
331
332
333std::vector<DRC_RE_LOADED_PANEL_ENTRY> DRC_RULE_LOADER::LoadRule( const DRC_RULE& aRule,
334 const wxString& aOriginalText )
335{
336 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries;
337
338 // Get condition expression if present
339 wxString condition;
340
341 if( aRule.m_Condition )
342 condition = aRule.m_Condition->GetExpression();
343
344 // Match the rule to panels
345 std::vector<DRC_PANEL_MATCH> matches = m_matcher.MatchRule( aRule );
346
347 for( DRC_PANEL_MATCH& match : matches )
348 {
349 if( match.panelType == PERMITTED_LAYERS && match.claimedConstraints.count( ASSERTION_CONSTRAINT ) )
350 {
351 const DRC_CONSTRAINT* assertion = findConstraint( aRule, ASSERTION_CONSTRAINT );
352
353 if( assertion && assertion->m_Test )
354 {
355 wxString expr = assertion->m_Test->GetExpression();
356
357 if( expr.Contains( wxS( "Orientation" ) ) && !expr.Contains( wxS( "Layer" ) ) )
358 match.panelType = ALLOWED_ORIENTATION;
359 }
360 }
361
362 auto constraintData = createConstraintData( match.panelType, aRule, match.claimedConstraints );
363
364 if( constraintData )
365 {
366 constraintData->SetRuleCondition( condition );
367
368 DRC_RE_LOADED_PANEL_ENTRY entry( match.panelType, constraintData, aRule.m_Name,
369 condition, aRule.m_Severity, aRule.m_LayerCondition );
370
371 // Preserve original layer source text for round-trip fidelity
372 entry.layerSource = aRule.m_LayerSource;
373
374 // Store original text only for the first entry to avoid duplication issues
375 if( entries.empty() )
376 entry.originalRuleText = aOriginalText;
377
378 entries.push_back( std::move( entry ) );
379 }
380 }
381
382 // If no matches, create a custom rule entry
383 if( entries.empty() )
384 {
385 auto customData = std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>();
386 customData->SetRuleName( aRule.m_Name );
387 customData->SetRuleCondition( condition );
388 customData->SetRuleText( aOriginalText );
389
390 DRC_RE_LOADED_PANEL_ENTRY entry( CUSTOM_RULE, customData, aRule.m_Name, condition,
391 aRule.m_Severity, aRule.m_LayerCondition );
392 entry.layerSource = aRule.m_LayerSource;
393 entry.originalRuleText = aOriginalText;
394 entries.push_back( std::move( entry ) );
395 }
396
397 return entries;
398}
399
400
401std::vector<DRC_RE_LOADED_PANEL_ENTRY> DRC_RULE_LOADER::LoadFromString( const wxString& aRulesText )
402{
403 std::vector<DRC_RE_LOADED_PANEL_ENTRY> allEntries;
404 std::vector<std::shared_ptr<DRC_RULE>> parsedRules;
405
406 wxString rulesText = aRulesText;
407
408 if( !rulesText.Contains( "(version" ) )
409 rulesText.Prepend( "(version 1)\n" );
410
411 try
412 {
413 DRC_RULES_PARSER parser( rulesText, "Rule Loader" );
414 parser.Parse( parsedRules, nullptr );
415 }
416 catch( const IO_ERROR& )
417 {
418 return allEntries;
419 }
420
421 for( const auto& rule : parsedRules )
422 {
423 // Extract the actual original text from the file content
424 wxString originalText = extractRuleText( aRulesText, rule->m_Name );
425
426 std::vector<DRC_RE_LOADED_PANEL_ENTRY> ruleEntries = LoadRule( *rule, originalText );
427
428 for( auto& entry : ruleEntries )
429 allEntries.push_back( std::move( entry ) );
430 }
431
432 return allEntries;
433}
434
435
436wxString DRC_RULE_LOADER::extractRuleText( const wxString& aContent, const wxString& aRuleName )
437{
438 // Search for the rule by name, handling both quoted and unquoted names.
439 // The quoted form includes the closing quote as a boundary so partial
440 // matches like "Clearance" vs "Clearance for BGA" are not possible.
441 // The unquoted form needs an explicit boundary check.
442 wxString quotedSearch = wxString::Format( wxS( "(rule \"%s\"" ), aRuleName );
443 wxString unquotedSearch = wxString::Format( wxS( "(rule %s" ), aRuleName );
444
445 size_t startPos = aContent.find( quotedSearch );
446
447 if( startPos == wxString::npos )
448 {
449 size_t pos = 0;
450
451 while( ( pos = aContent.find( unquotedSearch, pos ) ) != wxString::npos )
452 {
453 size_t afterMatch = pos + unquotedSearch.length();
454
455 if( afterMatch >= aContent.length()
456 || aContent[afterMatch] == ')'
457 || aContent[afterMatch] == ' '
458 || aContent[afterMatch] == '\n'
459 || aContent[afterMatch] == '\r'
460 || aContent[afterMatch] == '\t' )
461 {
462 startPos = pos;
463 break;
464 }
465
466 pos = afterMatch;
467 }
468 }
469
470 if( startPos == wxString::npos )
471 return wxEmptyString;
472
473 // Find the matching closing parenthesis by counting balanced parens
474 int parenCount = 0;
475 size_t endPos = startPos;
476 bool inString = false;
477 bool escaped = false;
478
479 for( size_t i = startPos; i < aContent.length(); ++i )
480 {
481 wxUniChar c = aContent[i];
482
483 if( escaped )
484 {
485 escaped = false;
486 continue;
487 }
488
489 if( c == '\\' )
490 {
491 escaped = true;
492 continue;
493 }
494
495 if( c == '"' )
496 {
497 inString = !inString;
498 continue;
499 }
500
501 if( inString )
502 continue;
503
504 if( c == '(' )
505 {
506 parenCount++;
507 }
508 else if( c == ')' )
509 {
510 parenCount--;
511
512 if( parenCount == 0 )
513 {
514 endPos = i;
515 break;
516 }
517 }
518 }
519
520 if( parenCount != 0 )
521 return wxEmptyString;
522
523 return aContent.Mid( startPos, endPos - startPos + 1 );
524}
525
526
527std::vector<DRC_RE_LOADED_PANEL_ENTRY> DRC_RULE_LOADER::LoadFile( const wxString& aPath )
528{
529 std::vector<DRC_RE_LOADED_PANEL_ENTRY> allEntries;
530
531 wxFFile file( aPath, "r" );
532
533 if( !file.IsOpened() )
534 return allEntries;
535
536 wxString content;
537 file.ReadAll( &content );
538 file.Close();
539
540 return LoadFromString( content );
541}
DRC_RULE_CONDITION * m_Test
Definition drc_rule.h:232
int m_DisallowFlags
Definition drc_rule.h:230
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:187
DRC_CONSTRAINT_T m_Type
Definition drc_rule.h:228
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
wxString GetExpression() const
static bool IsNumericInputType(const DRC_RULE_EDITOR_CONSTRAINT_NAME &aConstraintType)
static wxString GetConstraintCode(DRC_RULE_EDITOR_CONSTRAINT_NAME aConstraintType)
Translate a rule tree node type into the keyword used by the rules file for that constraint.
static std::shared_ptr< DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA > CreateNumericConstraintData(DRC_RULE_EDITOR_CONSTRAINT_NAME aType)
double toMM(int aValue)
Convert internal units (nanometers) to millimeters.
wxString extractRuleText(const wxString &aContent, const wxString &aRuleName)
Extract the complete original text of a rule from file content.
DRC_PANEL_MATCHER m_matcher
std::vector< DRC_RE_LOADED_PANEL_ENTRY > LoadRule(const DRC_RULE &aRule, const wxString &aOriginalText)
Load a single DRC_RULE and convert it to panel entries.
std::vector< DRC_RE_LOADED_PANEL_ENTRY > LoadFromString(const wxString &aRulesText)
Load rules from a text string.
const DRC_CONSTRAINT * findConstraint(const DRC_RULE &aRule, DRC_CONSTRAINT_T aType)
Find a constraint of a specific type in a rule.
std::vector< DRC_RE_LOADED_PANEL_ENTRY > LoadFile(const wxString &aPath)
Load all rules from a .kicad_dru file.
std::shared_ptr< DRC_RE_BASE_CONSTRAINT_DATA > createConstraintData(DRC_RULE_EDITOR_CONSTRAINT_NAME aPanel, const DRC_RULE &aRule, const std::set< DRC_CONSTRAINT_T > &aClaimedConstraints)
Create the appropriate constraint data object for a panel type.
SEVERITY m_Severity
Definition drc_rule.h:149
DRC_RULE_CONDITION * m_Condition
Definition drc_rule.h:147
LSET m_LayerCondition
Definition drc_rule.h:146
std::vector< DRC_CONSTRAINT > m_Constraints
Definition drc_rule.h:148
wxString m_Name
Definition drc_rule.h:144
wxString m_LayerSource
Definition drc_rule.h:145
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
T Min() const
Definition minoptmax.h:33
T Max() const
Definition minoptmax.h:34
T Opt() const
Definition minoptmax.h:35
@ DRC_DISALLOW_BURIED_VIAS
Definition drc_rule.h:93
@ DRC_DISALLOW_BLIND_VIAS
Definition drc_rule.h:92
@ DRC_DISALLOW_THROUGH_VIAS
Definition drc_rule.h:90
@ DRC_DISALLOW_MICRO_VIAS
Definition drc_rule.h:91
DRC_CONSTRAINT_T
Definition drc_rule.h:47
@ VIA_DIAMETER_CONSTRAINT
Definition drc_rule.h:70
@ DIFF_PAIR_GAP_CONSTRAINT
Definition drc_rule.h:73
@ DISALLOW_CONSTRAINT
Definition drc_rule.h:69
@ TRACK_WIDTH_CONSTRAINT
Definition drc_rule.h:59
@ MIN_RESOLVED_SPOKES_CONSTRAINT
Definition drc_rule.h:65
@ TEXT_THICKNESS_CONSTRAINT
Definition drc_rule.h:58
@ LENGTH_CONSTRAINT
Definition drc_rule.h:71
@ VIA_COUNT_CONSTRAINT
Definition drc_rule.h:76
@ MAX_UNCOUPLED_CONSTRAINT
Definition drc_rule.h:74
@ ASSERTION_CONSTRAINT
Definition drc_rule.h:79
@ SKEW_CONSTRAINT
Definition drc_rule.h:72
@ HOLE_SIZE_CONSTRAINT
Definition drc_rule.h:54
@ TEXT_HEIGHT_CONSTRAINT
Definition drc_rule.h:57
DRC_RULE_EDITOR_CONSTRAINT_NAME
@ ALLOWED_ORIENTATION
@ ROUTING_DIFF_PAIR
@ ABSOLUTE_LENGTH
@ PERMITTED_LAYERS
@ MINIMUM_TEXT_HEIGHT_AND_THICKNESS
@ MATCHED_LENGTH_DIFF_PAIR
Result of matching a panel to constraints.
Represents a rule loaded from a .kicad_dru file and mapped to a panel.
wxString originalRuleText
wxString layerSource
Original layer text: "inner", "outer", or layer name.