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
46
47
51
52
53double DRC_RULE_LOADER::toMM( int aValue )
54{
55 return aValue / 1000000.0;
56}
57
58
60{
61 for( const DRC_CONSTRAINT& constraint : aRule.m_Constraints )
62 {
63 if( constraint.m_Type == aType )
64 return &constraint;
65 }
66
67 return nullptr;
68}
69
70
71wxString DRC_RULE_LOADER::extractRuleBody( const wxString& aOriginalText )
72{
73 int ruleKeyword = aOriginalText.Find( wxS( "rule " ) );
74 if( ruleKeyword == wxNOT_FOUND )
75 return aOriginalText;
76
77 int bodyStart = aOriginalText.find( '(', ruleKeyword + 5 );
78 if( bodyStart == (int) wxString::npos )
79 return aOriginalText;
80
81 wxString body = aOriginalText.Mid( bodyStart );
82 body.Trim( true );
83
84 if( body.EndsWith( wxS( ")" ) ) )
85 body = body.Left( body.Length() - 1 );
86 body.Trim( true );
87
88 return body;
89}
90
91
92wxString DRC_RULE_LOADER::extractRuleComment( const wxString& aOriginalText )
93{
94 wxString comment;
95 wxArrayString lines = wxSplit( aOriginalText, '\n', '\0' );
96
97 for( const wxString& line : lines )
98 {
99 wxString trimmed = line;
100 trimmed.Trim( false );
101
102 if( trimmed.StartsWith( wxS( "#" ) ) )
103 {
104 wxString commentLine = trimmed.Mid( 1 );
105 commentLine.Trim( false );
106
107 if( !comment.IsEmpty() )
108 comment += wxS( "\n" );
109
110 comment += commentLine;
111 }
112 }
113
114 return comment;
115}
116
117
118wxString DRC_RULE_LOADER::cleanStrippedCondition( const wxString& aCondition )
119{
120 wxString cleaned = aCondition;
121
122 // Strip empty parentheses left over from removing conditions
123 wxString prev;
124 do
125 {
126 prev = cleaned;
127 cleaned.Replace( wxS( "()" ), wxS( "" ) );
128 } while( cleaned != prev );
129
130 cleaned.Replace( wxS( "&& &&" ), wxS( "&&" ) );
131 cleaned.Replace( wxS( "|| ||" ), wxS( "||" ) );
132 cleaned.Trim( true ).Trim( false );
133 if( cleaned.StartsWith( wxS( "&&" ) ) )
134 cleaned = cleaned.Mid( 2 ).Trim( false );
135 if( cleaned.EndsWith( wxS( "&&" ) ) )
136 cleaned = cleaned.Left( cleaned.Length() - 2 ).Trim( true );
137 if( cleaned.StartsWith( wxS( "||" ) ) )
138 cleaned = cleaned.Mid( 2 ).Trim( false );
139 if( cleaned.EndsWith( wxS( "||" ) ) )
140 cleaned = cleaned.Left( cleaned.Length() - 2 ).Trim( true );
141
142 return cleaned;
143}
144
145
146std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA>
148 const DRC_RULE& aRule,
149 const std::set<DRC_CONSTRAINT_T>& aClaimedConstraints )
150{
151 switch( aPanel )
152 {
153 case VIA_STYLE:
154 {
155 auto data = std::make_shared<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>();
156 data->SetRuleName( aRule.m_Name );
157 data->SetConstraintCode( "via_style" );
158
159 const DRC_CONSTRAINT* viaDia = findConstraint( aRule, VIA_DIAMETER_CONSTRAINT );
160 const DRC_CONSTRAINT* holeSize = findConstraint( aRule, HOLE_SIZE_CONSTRAINT );
161
162 if( viaDia )
163 {
164 data->SetMinViaDiameter( toMM( viaDia->GetValue().Min() ) );
165 data->SetMaxViaDiameter( toMM( viaDia->GetValue().Max() ) );
166 }
167
168 if( holeSize )
169 {
170 data->SetMinViaHoleSize( toMM( holeSize->GetValue().Min() ) );
171 data->SetMaxViaHoleSize( toMM( holeSize->GetValue().Max() ) );
172 }
173
174 if( aRule.m_Condition )
175 {
176 wxString expr = aRule.m_Condition->GetExpression();
177
178 if( expr.Contains( wxS( "'Micro'" ) ) )
179 data->SetViaType( VIA_STYLE_TYPE::MICRO );
180 else if( expr.Contains( wxS( "'Through'" ) ) )
181 data->SetViaType( VIA_STYLE_TYPE::THROUGH );
182 else if( expr.Contains( wxS( "'Blind'" ) ) )
183 data->SetViaType( VIA_STYLE_TYPE::BLIND );
184 else if( expr.Contains( wxS( "'Buried'" ) ) )
185 data->SetViaType( VIA_STYLE_TYPE::BURIED );
186
187 // Strip the via type condition so it doesn't duplicate on save
188 wxString cleanedCondition = expr;
189 cleanedCondition.Replace( wxS( "A.Via_Type == 'Micro'" ), wxS( "" ) );
190 cleanedCondition.Replace( wxS( "A.Via_Type == 'Through'" ), wxS( "" ) );
191 cleanedCondition.Replace( wxS( "A.Via_Type == 'Blind'" ), wxS( "" ) );
192 cleanedCondition.Replace( wxS( "A.Via_Type == 'Buried'" ), wxS( "" ) );
193
194 data->SetRuleCondition( cleanStrippedCondition( cleanedCondition ) );
195 }
196
197 return data;
198 }
199
201 {
202 auto data = std::make_shared<DRC_RE_ROUTING_DIFF_PAIR_CONSTRAINT_DATA>();
203 data->SetRuleName( aRule.m_Name );
204 data->SetConstraintCode( "diff_pair_gap" );
205
206 const DRC_CONSTRAINT* trackWidth = findConstraint( aRule, TRACK_WIDTH_CONSTRAINT );
207 const DRC_CONSTRAINT* diffGap = findConstraint( aRule, DIFF_PAIR_GAP_CONSTRAINT );
208 const DRC_CONSTRAINT* uncoupled = findConstraint( aRule, MAX_UNCOUPLED_CONSTRAINT );
209
210 if( trackWidth )
211 {
212 double opt = toMM( trackWidth->GetValue().Opt() );
213 double min = toMM( trackWidth->GetValue().Min() );
214 data->SetOptWidth( opt );
215 data->SetWidthTolerance( opt - min );
216 }
217
218 if( diffGap )
219 {
220 double opt = toMM( diffGap->GetValue().Opt() );
221 double min = toMM( diffGap->GetValue().Min() );
222 data->SetOptGap( opt );
223 data->SetGapTolerance( opt - min );
224 }
225
226 if( uncoupled )
227 {
228 data->SetMaxUncoupledLength( toMM( uncoupled->GetValue().Max() ) );
229 }
230
231 return data;
232 }
233
235 {
236 auto data = std::make_shared<DRC_RE_MINIMUM_TEXT_HEIGHT_THICKNESS_CONSTRAINT_DATA>();
237 data->SetRuleName( aRule.m_Name );
238 data->SetConstraintCode( "text_height" );
239
240 const DRC_CONSTRAINT* textHeight = findConstraint( aRule, TEXT_HEIGHT_CONSTRAINT );
241 const DRC_CONSTRAINT* textThickness = findConstraint( aRule, TEXT_THICKNESS_CONSTRAINT );
242
243 if( textHeight )
244 data->SetMinTextHeight( toMM( textHeight->GetValue().Min() ) );
245
246 if( textThickness )
247 data->SetMinTextThickness( toMM( textThickness->GetValue().Min() ) );
248
249 return data;
250 }
251
252 case ROUTING_WIDTH:
253 {
254 auto data = std::make_shared<DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA>();
255 data->SetRuleName( aRule.m_Name );
256 data->SetConstraintCode( "track_width" );
257
258 const DRC_CONSTRAINT* trackWidth = findConstraint( aRule, TRACK_WIDTH_CONSTRAINT );
259
260 if( trackWidth )
261 {
262 double opt = toMM( trackWidth->GetValue().Opt() );
263 double min = toMM( trackWidth->GetValue().Min() );
264 double max = toMM( trackWidth->GetValue().Max() );
265
266 data->SetOptWidth( opt );
267
268 if( opt > 0 )
269 {
270 double tolFromMin = opt - min;
271 double tolFromMax = max - opt;
272 data->SetWidthTolerance( std::max( tolFromMin, tolFromMax ) );
273 }
274 }
275
276 return data;
277 }
278
279 case ABSOLUTE_LENGTH:
280 {
281 auto data = std::make_shared<DRC_RE_ABSOLUTE_LENGTH_TWO_CONSTRAINT_DATA>();
282 data->SetRuleName( aRule.m_Name );
283 data->SetConstraintCode( "length" );
284
285 const DRC_CONSTRAINT* length = findConstraint( aRule, LENGTH_CONSTRAINT );
286
287 if( length )
288 {
289 double minMM = toMM( length->GetValue().Min() );
290 double optMM = toMM( length->GetValue().Opt() );
291 double maxMM = toMM( length->GetValue().Max() );
292 data->SetOptimumLength( optMM );
293 data->SetTolerance( ( maxMM - minMM ) / 2.0 );
294 }
295
296 return data;
297 }
298
300 {
301 auto data = std::make_shared<DRC_RE_MATCHED_LENGTH_DIFF_PAIR_CONSTRAINT_DATA>();
302 data->SetRuleName( aRule.m_Name );
303 data->SetConstraintCode( "length" );
304
305 const DRC_CONSTRAINT* length = findConstraint( aRule, LENGTH_CONSTRAINT );
306
307 if( length )
308 {
309 double minMM = toMM( length->GetValue().Min() );
310 double optMM = toMM( length->GetValue().Opt() );
311 double maxMM = toMM( length->GetValue().Max() );
312 data->SetOptimumLength( optMM );
313 data->SetTolerance( ( maxMM - minMM ) / 2.0 );
314 }
315
316 const DRC_CONSTRAINT* skew = findConstraint( aRule, SKEW_CONSTRAINT );
317
318 if( skew )
319 {
320 data->SetMaxSkew( toMM( skew->GetValue().Max() ) );
321 data->SetWithinDiffPairs( skew->GetOption( DRC_CONSTRAINT::OPTIONS::SKEW_WITHIN_DIFF_PAIRS ) );
322 }
323
324 return data;
325 }
326
327 case PERMITTED_LAYERS:
328 {
329 auto data = std::make_shared<DRC_RE_PERMITTED_LAYERS_CONSTRAINT_DATA>();
330 data->SetRuleName( aRule.m_Name );
331
332 const DRC_CONSTRAINT* constraint = findConstraint( aRule, ASSERTION_CONSTRAINT );
333
334 if( constraint && constraint->m_Test )
335 {
336 wxString expr = constraint->m_Test->GetExpression();
337
338 data->SetTopLayerEnabled( expr.Contains( wxS( "F.Cu" ) ) );
339 data->SetBottomLayerEnabled( expr.Contains( wxS( "B.Cu" ) ) );
340
341 // Check for layer references the panel can't represent
342 wxString remaining = expr;
343 remaining.Replace( wxS( "A.Layer == 'F.Cu'" ), wxS( "" ) );
344 remaining.Replace( wxS( "A.Layer == 'B.Cu'" ), wxS( "" ) );
345
346 if( remaining.Contains( wxS( "A.Layer" ) ) )
347 {
348 // Inner or non-standard layers — fall back to custom rule
349 auto customData = std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>();
350 customData->SetRuleName( aRule.m_Name );
351 return customData;
352 }
353 }
354
355 return data;
356 }
357
359 {
360 auto data = std::make_shared<DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA>();
361 data->SetRuleName( aRule.m_Name );
362 data->SetConstraintCode( wxS( "allowed_orientation" ) );
363
364 const DRC_CONSTRAINT* constraint = findConstraint( aRule, ASSERTION_CONSTRAINT );
365
366 if( constraint && constraint->m_Test )
367 {
368 wxString expr = constraint->m_Test->GetExpression();
369
370 data->SetIsZeroDegreesAllowed( expr.Contains( wxS( "== 0 deg" ) ) );
371 data->SetIsNinetyDegreesAllowed( expr.Contains( wxS( "== 90 deg" ) ) );
372 data->SetIsOneEightyDegreesAllowed( expr.Contains( wxS( "== 180 deg" ) ) );
373 data->SetIsTwoSeventyDegreesAllowed( expr.Contains( wxS( "== 270 deg" ) ) );
374
375 if( data->GetIsZeroDegreesAllowed() && data->GetIsNinetyDegreesAllowed()
376 && data->GetIsOneEightyDegreesAllowed() && data->GetIsTwoSeventyDegreesAllowed() )
377 {
378 data->SetIsAllDegreesAllowed( true );
379 return data;
380 }
381
382 if( data->GetIsZeroDegreesAllowed() || data->GetIsNinetyDegreesAllowed()
383 || data->GetIsOneEightyDegreesAllowed() || data->GetIsTwoSeventyDegreesAllowed() )
384 {
385 return data;
386 }
387
388 // Non-standard angles cannot be represented by the
389 // orientation panel, fall back to custom rule
390 auto customData = std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>();
391 customData->SetRuleName( aRule.m_Name );
392 return customData;
393 }
394 else
395 {
396 data->SetIsAllDegreesAllowed( true );
397 }
398
399 return data;
400 }
401
402 case VIAS_UNDER_SMD:
403 {
404 auto data = std::make_shared<DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA>();
405 data->SetRuleName( aRule.m_Name );
406 data->SetConstraintCode( wxS( "disallow_via" ) );
407
408 const DRC_CONSTRAINT* constraint = findConstraint( aRule, DISALLOW_CONSTRAINT );
409
410 if( constraint )
411 {
412 data->SetDisallowThroughVias( ( constraint->m_DisallowFlags & DRC_DISALLOW_THROUGH_VIAS ) != 0 );
413 data->SetDisallowMicroVias( ( constraint->m_DisallowFlags & DRC_DISALLOW_MICRO_VIAS ) != 0 );
414 data->SetDisallowBlindVias( ( constraint->m_DisallowFlags & DRC_DISALLOW_BLIND_VIAS ) != 0 );
415 data->SetDisallowBuriedVias( ( constraint->m_DisallowFlags & DRC_DISALLOW_BURIED_VIAS ) != 0 );
416 }
417
418 return data;
419 }
420
421 case CUSTOM_RULE:
422 {
423 auto data = std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>();
424 data->SetRuleName( aRule.m_Name );
425 return data;
426 }
427
428 default:
429 {
430 // For numeric input types, create a generic numeric constraint data
432 {
434 data->SetRuleName( aRule.m_Name );
435
436 wxString code = DRC_RULE_EDITOR_UTILS::GetConstraintCode( aPanel );
437 data->SetConstraintCode( code );
438
439 // Find the first matching constraint from the claimed set
440 for( DRC_CONSTRAINT_T type : aClaimedConstraints )
441 {
442 const DRC_CONSTRAINT* constraint = findConstraint( aRule, type );
443
444 if( constraint )
445 {
446 if( type == VIA_COUNT_CONSTRAINT )
447 data->SetNumericInputValue( constraint->GetValue().Max() );
448 else if( type == MIN_RESOLVED_SPOKES_CONSTRAINT )
449 data->SetNumericInputValue( constraint->GetValue().Min() );
450 else
451 data->SetNumericInputValue( toMM( constraint->GetValue().Min() ) );
452
453 break;
454 }
455 }
456
457 return data;
458 }
459
460 // Fallback to custom rule
461 auto data = std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>();
462 data->SetRuleName( aRule.m_Name );
463 return data;
464 }
465 }
466}
467
468
469std::vector<DRC_RE_LOADED_PANEL_ENTRY> DRC_RULE_LOADER::LoadRule( const DRC_RULE& aRule,
470 const wxString& aOriginalText )
471{
472 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries;
473
474 // Get condition expression if present
475 wxString condition;
476
477 if( aRule.m_Condition )
478 condition = aRule.m_Condition->GetExpression();
479
480 // Match the rule to panels
481 std::vector<DRC_PANEL_MATCH> matches = m_matcher.MatchRule( aRule );
482
483 for( DRC_PANEL_MATCH& match : matches )
484 {
485 if( match.panelType == PERMITTED_LAYERS && match.claimedConstraints.count( ASSERTION_CONSTRAINT ) )
486 {
487 const DRC_CONSTRAINT* assertion = findConstraint( aRule, ASSERTION_CONSTRAINT );
488
489 if( assertion && assertion->m_Test )
490 {
491 wxString expr = assertion->m_Test->GetExpression();
492
493 if( expr.Contains( wxS( "Orientation" ) ) && !expr.Contains( wxS( "Layer" ) ) )
494 match.panelType = ALLOWED_ORIENTATION;
495 }
496 }
497
498 if( match.panelType == SILK_TO_SILK_CLEARANCE && match.claimedConstraints.count( SILK_CLEARANCE_CONSTRAINT ) )
499 {
500 if( !condition.IsEmpty()
501 && ( condition.Contains( wxS( "L == 'F.Mask'" ) ) || condition.Contains( wxS( "L == 'B.Mask'" ) ) ) )
502 {
503 match.panelType = SILK_TO_SOLDERMASK_CLEARANCE;
504 }
505 else if( !condition.IsEmpty() && !condition.Contains( wxS( "L == 'F.SilkS'" ) )
506 && !condition.Contains( wxS( "L == 'B.SilkS'" ) ) )
507 {
508 match.panelType = CUSTOM_RULE;
509 }
510 }
511
512 auto constraintData = createConstraintData( match.panelType, aRule, match.claimedConstraints );
513
514 if( !constraintData )
515 continue;
516
517 // If createConstraintData returned a custom rule fallback (e.g. non-standard
518 // orientation angles), update the panel type to match the actual data type
519 auto customFallback = std::dynamic_pointer_cast<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>( constraintData );
520
521 if( customFallback && match.panelType != CUSTOM_RULE )
522 {
523 match.panelType = CUSTOM_RULE;
524 }
525
526 if( match.panelType == SILK_TO_SOLDERMASK_CLEARANCE )
527 {
528 wxString cleanedCondition = condition;
529
530 // New format: L == 'F.Mask' || L == 'B.Mask'
531 bool hasBothSides = condition.Contains( wxS( "L == 'F.Mask' || L == 'B.Mask'" ) );
532
533 if( hasBothSides )
534 {
535 constraintData->SetLayers( { F_SilkS, B_SilkS } );
536 constraintData->SetLayerSource( wxS( "" ) );
537
538 cleanedCondition.Replace( wxS( "L == 'F.Mask' || L == 'B.Mask'" ), wxS( "" ) );
539 }
540 else
541 {
542 bool isFront = condition.Contains( wxS( "F.Mask" ) );
543 PCB_LAYER_ID layer = isFront ? F_SilkS : B_SilkS;
544 constraintData->SetLayers( { layer } );
545 constraintData->SetLayerSource( isFront ? wxS( "F.SilkS" ) : wxS( "B.SilkS" ) );
546
547 cleanedCondition.Replace( wxS( "L == 'F.Mask'" ), wxS( "" ) );
548 cleanedCondition.Replace( wxS( "L == 'B.Mask'" ), wxS( "" ) );
549 }
550
551 constraintData->SetRuleCondition( cleanStrippedCondition( cleanedCondition ) );
552 }
553
554 if( match.panelType == SILK_TO_SILK_CLEARANCE )
555 {
556 wxString cleanedCondition = condition;
557
558 bool hasBothSides = condition.Contains( wxS( "L == 'F.SilkS' || L == 'B.SilkS'" ) );
559
560 if( hasBothSides )
561 {
562 constraintData->SetLayers( { F_SilkS, B_SilkS } );
563 constraintData->SetLayerSource( wxS( "" ) );
564
565 cleanedCondition.Replace( wxS( "L == 'F.SilkS' || L == 'B.SilkS'" ), wxS( "" ) );
566 }
567 else if( condition.Contains( wxS( "L == 'F.SilkS'" ) ) || condition.Contains( wxS( "L == 'B.SilkS'" ) ) )
568 {
569 bool isFront = condition.Contains( wxS( "F.SilkS" ) );
570 PCB_LAYER_ID layer = isFront ? F_SilkS : B_SilkS;
571 constraintData->SetLayers( { layer } );
572 constraintData->SetLayerSource( isFront ? wxS( "F.SilkS" ) : wxS( "B.SilkS" ) );
573
574 cleanedCondition.Replace( wxS( "L == 'F.SilkS'" ), wxS( "" ) );
575 cleanedCondition.Replace( wxS( "L == 'B.SilkS'" ), wxS( "" ) );
576 }
577
578 constraintData->SetRuleCondition( cleanStrippedCondition( cleanedCondition ) );
579 }
580
581 if( match.panelType == VIAS_UNDER_SMD )
582 {
583 wxString cleanedCondition = condition;
584
585 cleanedCondition.Replace( wxS( "A.Pad_Type == 'SMD'" ), wxS( "" ) );
586 cleanedCondition.Replace( wxS( "B.Pad_Type == 'SMD'" ), wxS( "" ) );
587
588 constraintData->SetRuleCondition( cleanStrippedCondition( cleanedCondition ) );
589 }
590
591 if( match.panelType == CUSTOM_RULE && customFallback )
592 {
593 customFallback->SetRuleText( extractRuleBody( aOriginalText ) );
594 }
595
596 if( match.panelType != VIA_STYLE && match.panelType != SILK_TO_SOLDERMASK_CLEARANCE
597 && match.panelType != SILK_TO_SILK_CLEARANCE && match.panelType != VIAS_UNDER_SMD )
598 constraintData->SetRuleCondition( condition );
599
600 DRC_RE_LOADED_PANEL_ENTRY entry( match.panelType, constraintData, aRule.m_Name, condition, aRule.m_Severity,
601 aRule.m_LayerCondition );
602
603 // Preserve original layer source text for round-trip fidelity
604 wxString source = aRule.m_LayerSource;
605 if( source.StartsWith( wxS( "'" ) ) && source.EndsWith( wxS( "'" ) ) )
606 source = source.Mid( 1, source.Length() - 2 );
607 entry.layerSource = source;
608
609 wxString comment = extractRuleComment( aOriginalText );
610 if( !comment.IsEmpty() )
611 constraintData->SetComment( comment );
612
613 // Store original text only for the first entry to avoid duplication issues
614 if( entries.empty() )
615 entry.originalRuleText = aOriginalText;
616
617 entries.push_back( std::move( entry ) );
618 }
619
620 // If no matches, create a custom rule entry
621 if( entries.empty() )
622 {
623 auto customData = std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>();
624 customData->SetRuleName( aRule.m_Name );
625 customData->SetRuleCondition( condition );
626
627 wxString comment = extractRuleComment( aOriginalText );
628 if( !comment.IsEmpty() )
629 customData->SetComment( comment );
630
631 customData->SetRuleText( extractRuleBody( aOriginalText ) );
632
633 DRC_RE_LOADED_PANEL_ENTRY entry( CUSTOM_RULE, customData, aRule.m_Name, condition,
634 aRule.m_Severity, aRule.m_LayerCondition );
635 wxString source = aRule.m_LayerSource;
636 if( source.StartsWith( wxS( "'" ) ) && source.EndsWith( wxS( "'" ) ) )
637 source = source.Mid( 1, source.Length() - 2 );
638 entry.layerSource = source;
639 entry.originalRuleText = aOriginalText;
640 entries.push_back( std::move( entry ) );
641 }
642
643 return entries;
644}
645
646
647std::vector<DRC_RE_LOADED_PANEL_ENTRY> DRC_RULE_LOADER::LoadFromString( const wxString& aRulesText )
648{
649 std::vector<DRC_RE_LOADED_PANEL_ENTRY> allEntries;
650 std::vector<std::shared_ptr<DRC_RULE>> parsedRules;
651
652 wxString rulesText = aRulesText;
653
654 if( !rulesText.Contains( "(version" ) )
655 rulesText.Prepend( "(version 1)\n" );
656
657 try
658 {
659 DRC_RULES_PARSER parser( rulesText, "Rule Loader" );
660 parser.Parse( parsedRules, nullptr );
661 }
662 catch( const IO_ERROR& )
663 {
664 return allEntries;
665 }
666
667 for( const auto& rule : parsedRules )
668 {
669 // Extract the actual original text from the file content
670 wxString originalText = extractRuleText( aRulesText, rule->m_Name );
671
672 std::vector<DRC_RE_LOADED_PANEL_ENTRY> ruleEntries = LoadRule( *rule, originalText );
673
674 for( auto& entry : ruleEntries )
675 allEntries.push_back( std::move( entry ) );
676 }
677
678 return allEntries;
679}
680
681
682wxString DRC_RULE_LOADER::extractRuleText( const wxString& aContent, const wxString& aRuleName )
683{
684 // Search for the rule by name, handling both quoted and unquoted names.
685 // The quoted form includes the closing quote as a boundary so partial
686 // matches like "Clearance" vs "Clearance for BGA" are not possible.
687 // The unquoted form needs an explicit boundary check.
688 wxString quotedSearch = wxString::Format( wxS( "(rule \"%s\"" ), aRuleName );
689 wxString unquotedSearch = wxString::Format( wxS( "(rule %s" ), aRuleName );
690
691 size_t startPos = aContent.find( quotedSearch );
692
693 if( startPos == wxString::npos )
694 {
695 size_t pos = 0;
696
697 while( ( pos = aContent.find( unquotedSearch, pos ) ) != wxString::npos )
698 {
699 size_t afterMatch = pos + unquotedSearch.length();
700
701 if( afterMatch >= aContent.length()
702 || aContent[afterMatch] == ')'
703 || aContent[afterMatch] == ' '
704 || aContent[afterMatch] == '\n'
705 || aContent[afterMatch] == '\r'
706 || aContent[afterMatch] == '\t' )
707 {
708 startPos = pos;
709 break;
710 }
711
712 pos = afterMatch;
713 }
714 }
715
716 if( startPos == wxString::npos )
717 return wxEmptyString;
718
719 // Find the matching closing parenthesis by counting balanced parens
720 int parenCount = 0;
721 size_t endPos = startPos;
722 bool inString = false;
723 bool escaped = false;
724
725 for( size_t i = startPos; i < aContent.length(); ++i )
726 {
727 wxUniChar c = aContent[i];
728
729 if( escaped )
730 {
731 escaped = false;
732 continue;
733 }
734
735 if( c == '\\' )
736 {
737 escaped = true;
738 continue;
739 }
740
741 if( c == '"' )
742 {
743 inString = !inString;
744 continue;
745 }
746
747 if( inString )
748 continue;
749
750 if( c == '(' )
751 {
752 parenCount++;
753 }
754 else if( c == ')' )
755 {
756 parenCount--;
757
758 if( parenCount == 0 )
759 {
760 endPos = i;
761 break;
762 }
763 }
764 }
765
766 if( parenCount != 0 )
767 return wxEmptyString;
768
769 return aContent.Mid( startPos, endPos - startPos + 1 );
770}
771
772
773std::vector<DRC_RE_LOADED_PANEL_ENTRY> DRC_RULE_LOADER::LoadFile( const wxString& aPath )
774{
775 std::vector<DRC_RE_LOADED_PANEL_ENTRY> allEntries;
776
777 wxFFile file( aPath, "r" );
778
779 if( !file.IsOpened() )
780 return allEntries;
781
782 wxString content;
783 file.ReadAll( &content );
784 file.Close();
785
786 return LoadFromString( content );
787}
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
bool GetOption(OPTIONS option) const
Definition drc_rule.h:220
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.
wxString extractRuleComment(const wxString &aOriginalText)
Extract comment lines from a rule.
const DRC_CONSTRAINT * findConstraint(const DRC_RULE &aRule, DRC_CONSTRAINT_T aType)
Find a constraint of a specific type in a rule.
wxString extractRuleBody(const wxString &aOriginalText)
Extract the body of a rule from its original text, stripping the (rule "name" ...) wrapper.
std::vector< DRC_RE_LOADED_PANEL_ENTRY > LoadFile(const wxString &aPath)
Load all rules from a .kicad_dru file.
wxString cleanStrippedCondition(const wxString &aCondition)
Clean up a condition string after auto-generated tokens have been removed.
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
@ SILK_CLEARANCE_CONSTRAINT
Definition drc_rule.h:56
@ 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
@ SILK_TO_SILK_CLEARANCE
@ ROUTING_DIFF_PAIR
@ SILK_TO_SOLDERMASK_CLEARANCE
@ ABSOLUTE_LENGTH
@ PERMITTED_LAYERS
@ MINIMUM_TEXT_HEIGHT_AND_THICKNESS
@ MATCHED_LENGTH_DIFF_PAIR
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ F_SilkS
Definition layer_ids.h:100
@ B_SilkS
Definition layer_ids.h:101
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.