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