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