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