KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_drc_rule_editor.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 <boost/test/unit_test.hpp>
21#include <wx/ffile.h>
22#include <core/typeinfo.h>
34#include <drc/drc_rule.h>
43
44BOOST_AUTO_TEST_SUITE( DRC_RULE_EDITOR )
45
46BOOST_AUTO_TEST_CASE( RoundTripViaStyle )
47{
48 DRC_RE_VIA_STYLE_CONSTRAINT_DATA original( 0, 0, "My_Via_Rule", 0.5, 0.8, 0.2, 0.4 );
49 original.SetConstraintCode( "via_style" );
50 original.SetRuleCondition( "A.NetClass == 'Power'" );
51
53 ctx.ruleName = original.GetRuleName();
55 ctx.constraintCode = original.GetConstraintCode();
56
57 wxString ruleText = original.GenerateRule( ctx );
58
59 auto parsedRules = DRC_RULE_EDITOR_UTILS::ParseRules( ruleText );
60
61 BOOST_REQUIRE_EQUAL( parsedRules.size(), 1 );
62 auto parsed = std::dynamic_pointer_cast<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>( parsedRules[0] );
63 BOOST_REQUIRE( parsed );
64
65 BOOST_CHECK_EQUAL( parsed->GetRuleName(), original.GetRuleName() );
66 BOOST_CHECK_EQUAL( parsed->GetRuleCondition(), original.GetRuleCondition() );
67 BOOST_CHECK_CLOSE( parsed->GetMinViaDiameter(), original.GetMinViaDiameter(), 0.0001 );
68 BOOST_CHECK_CLOSE( parsed->GetMaxViaDiameter(), original.GetMaxViaDiameter(), 0.0001 );
69 BOOST_CHECK_CLOSE( parsed->GetMinViaHoleSize(), original.GetMinViaHoleSize(), 0.0001 );
70 BOOST_CHECK_CLOSE( parsed->GetMaxViaHoleSize(), original.GetMaxViaHoleSize(), 0.0001 );
71}
72
73BOOST_AUTO_TEST_CASE( RoundTripRoutingWidth )
74{
75 DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA original( 0, 0, "My_Track_Rule", 0.3, 0.1 );
76 original.SetConstraintCode( "track_width" );
77 original.SetRuleCondition( "A.NetClass == 'Signal'" );
78
80 ctx.ruleName = original.GetRuleName();
82 ctx.constraintCode = original.GetConstraintCode();
83
84 wxString ruleText = original.GenerateRule( ctx );
85
86 auto parsedRules = DRC_RULE_EDITOR_UTILS::ParseRules( ruleText );
87
88 BOOST_REQUIRE_EQUAL( parsedRules.size(), 1 );
89 auto parsed = std::dynamic_pointer_cast<DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA>( parsedRules[0] );
90 BOOST_REQUIRE( parsed );
91
92 BOOST_CHECK_EQUAL( parsed->GetRuleName(), original.GetRuleName() );
93 BOOST_CHECK_EQUAL( parsed->GetRuleCondition(), original.GetRuleCondition() );
94 BOOST_CHECK_CLOSE( parsed->GetOptWidth(), original.GetOptWidth(), 0.0001 );
95 BOOST_CHECK_CLOSE( parsed->GetWidthTolerance(), original.GetWidthTolerance(), 0.0001 );
96}
97
99{
100 std::vector<std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA>> rules;
101
102 auto rule1 = std::make_shared<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>( 0, 0, "ViaRule", 0.5, 0.8, 0.2, 0.4 );
103 rule1->SetConstraintCode( "via_style" );
104 rule1->SetRuleCondition( "A.NetClass == 'Power'" );
105 rules.push_back( rule1 );
106
107 auto rule2 = std::make_shared<DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA>( 0, 0, "TrackRule", 0.3, 0.1 );
108 rule2->SetConstraintCode( "track_width" );
109 rule2->SetRuleCondition( "A.NetClass == 'Signal'" );
110 rules.push_back( rule2 );
111
112 wxString filename = "test_rules.dru";
113
114 // We pass nullptr for board, so layers won't be saved, which is fine for this test
115 bool result = DRC_RULE_EDITOR_UTILS::SaveRules( filename, rules, nullptr );
117
118 wxFFile file( filename );
119 BOOST_REQUIRE( file.IsOpened() );
120 wxString content;
121 file.ReadAll( &content );
122
123 // Verify content
124 BOOST_CHECK( content.Contains( "(version 2)" ) );
125 BOOST_CHECK( content.Contains( "(rule ViaRule" ) );
126 BOOST_CHECK( content.Contains( "(constraint via_diameter" ) );
127 BOOST_CHECK( content.Contains( "(constraint hole_size" ) );
128 BOOST_CHECK( content.Contains( "(rule TrackRule" ) );
129 BOOST_CHECK( content.Contains( "(constraint track_width" ) );
130
131 // Clean up
132 wxRemoveFile( filename );
133}
134
135BOOST_AUTO_TEST_CASE( SaveRulesToFileLogic )
136{
137 // Mimic m_ruleTreeNodeDatas
138 std::vector<RULE_TREE_NODE> ruleTreeNodeDatas;
139
140 // Add a rule node
141 RULE_TREE_NODE node;
142 node.m_nodeId = 1;
143 node.m_nodeName = "TestRule";
144 node.m_nodeType = RULE; // 4
145
146 auto data = std::make_shared<DRC_RE_BASE_CONSTRAINT_DATA>( 1, 0, "TestRule" );
147 data->SetConstraintCode( "clearance" );
148 node.m_nodeData = data;
149
150 ruleTreeNodeDatas.push_back( node );
151
152 // Mimic SaveRulesToFile logic
153 std::vector<std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA>> rules;
154
155 for( const RULE_TREE_NODE& n : ruleTreeNodeDatas )
156 {
157 if( n.m_nodeType != RULE )
158 continue;
159
160 auto d = std::dynamic_pointer_cast<DRC_RE_BASE_CONSTRAINT_DATA>( n.m_nodeData );
161
162 if( d )
163 rules.push_back( d );
164 }
165
166 BOOST_CHECK_EQUAL( rules.size(), 1 );
167}
168
169BOOST_AUTO_TEST_CASE( SaveNumericRules )
170{
171 auto rule = std::make_shared<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>( 0, 0, 0.2, "ClearanceRule" );
172 rule->SetConstraintCode( "clearance" );
173
175 ctx.ruleName = rule->GetRuleName();
176 ctx.constraintCode = rule->GetConstraintCode();
177
178 wxString ruleText = rule->GenerateRule( ctx );
179
180 BOOST_CHECK( ruleText.Contains( "(constraint clearance (min 0.2mm))" ) );
181
182 // Test via_count (max, unitless)
183 auto rule2 = std::make_shared<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>( 0, 0, 10, "ViaCountRule" );
184 rule2->SetConstraintCode( "via_count" );
185 ctx.ruleName = rule2->GetRuleName();
186 ctx.constraintCode = rule2->GetConstraintCode();
187
188 ruleText = rule2->GenerateRule( ctx );
189 BOOST_CHECK( ruleText.Contains( "(constraint via_count (max 10))" ) );
190
191 // Test track_angle (min, deg)
192 auto rule3 = std::make_shared<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>( 0, 0, 45, "AngleRule" );
193 rule3->SetConstraintCode( "track_angle" );
194 ctx.ruleName = rule3->GetRuleName();
195 ctx.constraintCode = rule3->GetConstraintCode();
196
197 ruleText = rule3->GenerateRule( ctx );
198 BOOST_CHECK( ruleText.Contains( "(constraint track_angle (min 45deg))" ) );
199}
200
201BOOST_AUTO_TEST_CASE( SaveBoolRules )
202{
203 auto rule = std::make_shared<DRC_RE_BOOL_INPUT_CONSTRAINT_DATA>( 0, 0, true, "DisallowTestRule" );
204 rule->SetConstraintCode( "disallow" );
205
207 ctx.ruleName = rule->GetRuleName();
208 ctx.constraintCode = rule->GetConstraintCode();
209
210 wxString ruleText = rule->GenerateRule( ctx );
211
212 BOOST_CHECK( ruleText.Contains( "(constraint disallow)" ) );
213
214 // Test false value (should not generate constraint)
215 auto rule2 = std::make_shared<DRC_RE_BOOL_INPUT_CONSTRAINT_DATA>( 0, 0, false, "NoDisallowTestRule" );
216 rule2->SetConstraintCode( "disallow" );
217 ctx.ruleName = rule2->GetRuleName();
218 ctx.constraintCode = rule2->GetConstraintCode();
219
220 ruleText = rule2->GenerateRule( ctx );
221 BOOST_CHECK( ruleText.IsEmpty() );
222}
223
224BOOST_AUTO_TEST_CASE( ParseRulesCategories )
225{
226 wxString rules =
227 "(version 1)\n"
228 "(rule \"Clearance Rule\"\n"
229 "\t(constraint clearance (min 0.2mm))\n"
230 "\t(condition \"A.NetClass == 'Power'\"))\n"
231 "(rule \"Via Style Rule\"\n"
232 "\t(constraint via_diameter (min 0.5mm))\n"
233 "\t(constraint hole_size (min 0.3mm))\n"
234 "\t(condition \"A.NetClass == 'Power'\"))\n";
235
236 auto parsedRules = DRC_RULE_EDITOR_UTILS::ParseRules( rules );
237
238 BOOST_REQUIRE_EQUAL( parsedRules.size(), 2 );
239
240 // Check Clearance Rule
241 auto clearanceRule = parsedRules[0];
242 BOOST_CHECK_EQUAL( clearanceRule->GetRuleName(), "Clearance Rule" );
243 BOOST_CHECK_EQUAL( clearanceRule->GetConstraintCode(), "clearance" );
244
245 // Verify it is a numeric constraint data
246 auto numericData = std::dynamic_pointer_cast<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>( clearanceRule );
247 BOOST_REQUIRE( numericData );
248 BOOST_CHECK_CLOSE( numericData->GetNumericInputValue(), 0.2, 0.0001 );
249
250 // Check Via Style Rule
251 auto viaStyleRule = parsedRules[1];
252 BOOST_CHECK_EQUAL( viaStyleRule->GetRuleName(), "Via Style Rule" );
253 BOOST_CHECK_EQUAL( viaStyleRule->GetConstraintCode(), "via_style" );
254
255 auto viaData = std::dynamic_pointer_cast<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>( viaStyleRule );
256 BOOST_REQUIRE( viaData );
257 BOOST_CHECK_CLOSE( viaData->GetMinViaDiameter(), 0.5, 0.0001 );
258 BOOST_CHECK_CLOSE( viaData->GetMinViaHoleSize(), 0.3, 0.0001 );
259}
260
261BOOST_AUTO_TEST_CASE( FactoryRegistration )
262{
263 // Register a custom parser for 'clearance' to override default
264 bool parserCalled = false;
266 [&]( const std::shared_ptr<DRC_RULE>& aRule ) -> std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA>
267 {
268 if( aRule->FindConstraint( CLEARANCE_CONSTRAINT ) )
269 {
270 parserCalled = true;
271 auto data = std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>( 0, 0, aRule->m_Name );
272 data->SetConstraintCode( "custom_clearance" );
273 return data;
274 }
275 return nullptr;
276 } );
277
278 wxString ruleText = "(version 1) (rule \"Test Rule\" (constraint clearance (min 1.0mm)) (condition \"A.Type == 'Pad'\"))";
279 auto parsedRules = DRC_RULE_EDITOR_UTILS::ParseRules( ruleText );
280
281 BOOST_CHECK( parserCalled );
282 BOOST_REQUIRE_EQUAL( parsedRules.size(), 1 );
283 BOOST_CHECK_EQUAL( parsedRules[0]->GetConstraintCode(), "custom_clearance" );
284}
285
286BOOST_AUTO_TEST_CASE( ValidateViaStyleValid )
287{
288 DRC_RE_VIA_STYLE_CONSTRAINT_DATA data( 0, 0, "ValidRule", 0.5, 0.8, 0.2, 0.4 );
289
291
292 BOOST_CHECK( result.isValid );
293 BOOST_CHECK( result.errors.empty() );
294}
295
296BOOST_AUTO_TEST_CASE( ValidateViaStyleInvalidMinGreaterThanMax )
297{
298 // min > max for via diameter
299 DRC_RE_VIA_STYLE_CONSTRAINT_DATA data( 0, 0, "InvalidRule", 0.9, 0.5, 0.2, 0.4 );
300
302
303 BOOST_CHECK( !result.isValid );
304 BOOST_CHECK( !result.errors.empty() );
305
306 // Should have error about min > max
307 bool foundMinMaxError = false;
308 for( const auto& error : result.errors )
309 {
310 if( error.find( "Minimum Via Diameter cannot be greater than Maximum Via Diameter" ) != std::string::npos )
311 foundMinMaxError = true;
312 }
313 BOOST_CHECK( foundMinMaxError );
314}
315
316BOOST_AUTO_TEST_CASE( ValidateViaStyleInvalidNegativeValues )
317{
318 // Negative values
319 DRC_RE_VIA_STYLE_CONSTRAINT_DATA data( 0, 0, "NegativeRule", -0.5, 0.8, 0.2, 0.4 );
320
322
323 BOOST_CHECK( !result.isValid );
324 BOOST_CHECK( !result.errors.empty() );
325
326 // Should have error about negative value
327 bool foundNegativeError = false;
328 for( const auto& error : result.errors )
329 {
330 if( error.find( "must be greater than 0" ) != std::string::npos )
331 foundNegativeError = true;
332 }
333 BOOST_CHECK( foundNegativeError );
334}
335
336BOOST_AUTO_TEST_CASE( FactoryOverwrite )
337{
338 // Register a parser
340 []( const std::shared_ptr<DRC_RULE>& aRule ) -> std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA> {
341 if( aRule->m_Name == "Test" )
342 return std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>( 0, 0, aRule->m_Name + "_1" );
343 return nullptr;
344 } );
345
346 // Overwrite it (prepend)
348 []( const std::shared_ptr<DRC_RULE>& aRule ) -> std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA> {
349 if( aRule->m_Name == "Test" )
350 return std::make_shared<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>( 0, 0, aRule->m_Name + "_2" );
351 return nullptr;
352 } );
353
354 wxString ruleText = "(version 1) (rule \"Test\" (constraint clearance (min 1.0mm)))";
355 auto parsedRules = DRC_RULE_EDITOR_UTILS::ParseRules( ruleText );
356
357 BOOST_REQUIRE_EQUAL( parsedRules.size(), 1 );
358 BOOST_CHECK_EQUAL( parsedRules[0]->GetRuleName(), "Test_2" );
359}
360
361BOOST_AUTO_TEST_CASE( ValidateAbsLengthTwoValid )
362{
363 // Valid: min < opt < max, all positive
364 DRC_RE_ABSOLUTE_LENGTH_TWO_CONSTRAINT_DATA data( 0, 0, 3.0, 2.0, "ValidLengthRule" );
365
367
368 BOOST_CHECK( result.isValid );
369 BOOST_CHECK( result.errors.empty() );
370}
371
372BOOST_AUTO_TEST_CASE( ValidateAbsLengthTwoInvalid )
373{
374 // Invalid: tolerance too large, resulting minimum length is not positive
375 DRC_RE_ABSOLUTE_LENGTH_TWO_CONSTRAINT_DATA data1( 0, 0, 2.0, 5.0, "InvalidToleranceRule" );
376
377 VALIDATION_RESULT result1 = data1.Validate();
378
379 BOOST_CHECK( !result1.isValid );
380 BOOST_CHECK( !result1.errors.empty() );
381
382 bool foundToleranceError = false;
383
384 for( const auto& error : result1.errors )
385 {
386 if( error.find( "Tolerance is too large" ) != std::string::npos )
387 foundToleranceError = true;
388 }
389
390 BOOST_CHECK( foundToleranceError );
391
392 // Invalid: negative tolerance
393 DRC_RE_ABSOLUTE_LENGTH_TWO_CONSTRAINT_DATA data2( 0, 0, 3.0, -1.0, "NegativeToleranceRule" );
394
395 VALIDATION_RESULT result2 = data2.Validate();
396
397 BOOST_CHECK( !result2.isValid );
398
399 bool foundNegativeError = false;
400
401 for( const auto& error : result2.errors )
402 {
403 if( error.find( "Tolerance must be greater than or equal to 0" ) != std::string::npos )
404 foundNegativeError = true;
405 }
406
407 BOOST_CHECK( foundNegativeError );
408}
409
410BOOST_AUTO_TEST_CASE( ValidateDiffPairValid )
411{
412 // Valid: all positive, min <= preferred <= max for width and gap
413 // Constructor: id, parentId, ruleName, optWidth, widthTolerance, optGap, gapTolerance, maxUncoupledLength, maxSkew
414 DRC_RE_ROUTING_DIFF_PAIR_CONSTRAINT_DATA data( 0, 0, "ValidDiffPairRule", 0.3, 0.1, 0.15, 0.05, 10.0 );
415
417
418 BOOST_CHECK( result.isValid );
419 BOOST_CHECK( result.errors.empty() );
420}
421
422BOOST_AUTO_TEST_CASE( ValidateDiffPairInvalid )
423{
424 // Invalid: min width > max width
425 DRC_RE_ROUTING_DIFF_PAIR_CONSTRAINT_DATA data1( 0, 0, "InvalidWidthRule", 0.3, 0.5, 0.15, 0.05, 10.0 );
426
427 VALIDATION_RESULT result1 = data1.Validate();
428
429 BOOST_CHECK( !result1.isValid );
430 BOOST_CHECK( !result1.errors.empty() );
431
432 bool foundWidthError = false;
433 for( const auto& error : result1.errors )
434 {
435 if( error.find( "Width Tolerance must be less than Optimum Width" ) != std::string::npos )
436 foundWidthError = true;
437 }
438 BOOST_CHECK( foundWidthError );
439
440 // Invalid: negative gap
441 DRC_RE_ROUTING_DIFF_PAIR_CONSTRAINT_DATA data2( 0, 0, "NegativeGapRule", 0.3, 0.1, 0.15, -0.1, 10.0 );
442
443 VALIDATION_RESULT result2 = data2.Validate();
444
445 BOOST_CHECK( !result2.isValid );
446
447 bool foundNegativeError = false;
448 for( const auto& error : result2.errors )
449 {
450 if( error.find( "must be greater than or equal to 0" ) != std::string::npos )
451 foundNegativeError = true;
452 }
453 BOOST_CHECK( foundNegativeError );
454}
455
456BOOST_AUTO_TEST_CASE( ValidatePermittedLayersValid )
457{
458 // Valid: at least one layer is selected (top layer enabled)
459 DRC_RE_PERMITTED_LAYERS_CONSTRAINT_DATA data1( 0, 0, "ValidTopLayerRule", true, false );
460
461 VALIDATION_RESULT result1 = data1.Validate();
462
463 BOOST_CHECK( result1.isValid );
464 BOOST_CHECK( result1.errors.empty() );
465
466 // Valid: at least one layer is selected (bottom layer enabled)
467 DRC_RE_PERMITTED_LAYERS_CONSTRAINT_DATA data2( 0, 0, "ValidBottomLayerRule", false, true );
468
469 VALIDATION_RESULT result2 = data2.Validate();
470
471 BOOST_CHECK( result2.isValid );
472 BOOST_CHECK( result2.errors.empty() );
473
474 // Valid: both layers enabled
475 DRC_RE_PERMITTED_LAYERS_CONSTRAINT_DATA data3( 0, 0, "ValidBothLayersRule", true, true );
476
477 VALIDATION_RESULT result3 = data3.Validate();
478
479 BOOST_CHECK( result3.isValid );
480 BOOST_CHECK( result3.errors.empty() );
481}
482
483BOOST_AUTO_TEST_CASE( ValidatePermittedLayersInvalid )
484{
485 // Invalid: no layers selected
486 DRC_RE_PERMITTED_LAYERS_CONSTRAINT_DATA data( 0, 0, "NoLayersRule", false, false );
487
489
490 BOOST_CHECK( !result.isValid );
491 BOOST_CHECK( !result.errors.empty() );
492
493 bool foundLayerError = false;
494 for( const auto& error : result.errors )
495 {
496 if( error.find( "At least one layer must be selected" ) != std::string::npos )
497 foundLayerError = true;
498 }
499 BOOST_CHECK( foundLayerError );
500}
501
502BOOST_AUTO_TEST_CASE( ValidateNumericInputValid )
503{
504 DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA data( 0, 0, 0.5, "ValidRule" );
505
507
508 BOOST_CHECK( result.isValid );
509 BOOST_CHECK( result.errors.empty() );
510}
511
512BOOST_AUTO_TEST_CASE( ValidateNumericInputInvalidNegative )
513{
514 DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA data( 0, 0, -0.5, "InvalidRule" );
515
517
518 BOOST_CHECK( !result.isValid );
519 BOOST_CHECK( !result.errors.empty() );
520
521 // Should have error about value not being greater than 0
522 bool foundError = false;
523 for( const auto& error : result.errors )
524 {
525 if( error.find( "must be greater than 0" ) != std::string::npos )
526 foundError = true;
527 }
528 BOOST_CHECK( foundError );
529}
530
531BOOST_AUTO_TEST_CASE( ValidateRoutingWidthValid )
532{
533 DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA data( 0, 0, "ValidRule", 0.3, 0.1 );
534
536
537 BOOST_CHECK( result.isValid );
538 BOOST_CHECK( result.errors.empty() );
539}
540
541BOOST_AUTO_TEST_CASE( ValidateRoutingWidthInvalidToleranceTooLarge )
542{
543 // tolerance >= opt width
544 DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA data( 0, 0, "InvalidRule", 0.3, 0.5 );
545
547
548 BOOST_CHECK( !result.isValid );
549 BOOST_CHECK( !result.errors.empty() );
550
551 bool foundError = false;
552 for( const auto& error : result.errors )
553 {
554 if( error.find( "Width Tolerance must be less than Optimum Width" ) != std::string::npos )
555 foundError = true;
556 }
557 BOOST_CHECK( foundError );
558}
559
560BOOST_AUTO_TEST_CASE( ValidateAllowedOrientationValid )
561{
562 // Valid: at least one orientation is selected (0 degrees)
563 DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA data1( 0, 0, true, false, false, false, false, "ValidZeroDegRule" );
564
565 VALIDATION_RESULT result1 = data1.Validate();
566
567 BOOST_CHECK( result1.isValid );
568 BOOST_CHECK( result1.errors.empty() );
569
570 // Valid: 90 degrees selected
571 DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA data2( 0, 0, false, true, false, false, false, "ValidNinetyDegRule" );
572
573 VALIDATION_RESULT result2 = data2.Validate();
574
575 BOOST_CHECK( result2.isValid );
576 BOOST_CHECK( result2.errors.empty() );
577
578 // Valid: all degrees selected
579 DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA data3( 0, 0, false, false, false, false, true, "ValidAllDegreesRule" );
580
581 VALIDATION_RESULT result3 = data3.Validate();
582
583 BOOST_CHECK( result3.isValid );
584 BOOST_CHECK( result3.errors.empty() );
585
586 // Valid: multiple orientations selected
587 DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA data4( 0, 0, true, true, false, false, false, "ValidMultipleRule" );
588
589 VALIDATION_RESULT result4 = data4.Validate();
590
591 BOOST_CHECK( result4.isValid );
592 BOOST_CHECK( result4.errors.empty() );
593}
594
595BOOST_AUTO_TEST_CASE( ValidateAllowedOrientationInvalid )
596{
597 // Invalid: no orientation selected
598 DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA data( 0, 0, false, false, false, false, false, "NoOrientationRule" );
599
601
602 BOOST_CHECK( !result.isValid );
603 BOOST_CHECK( !result.errors.empty() );
604
605 bool foundOrientationError = false;
606 for( const auto& error : result.errors )
607 {
608 if( error.find( "At least one orientation must be selected" ) != std::string::npos )
609 foundOrientationError = true;
610 }
611 BOOST_CHECK( foundOrientationError );
612}
613
614BOOST_AUTO_TEST_CASE( ValidateBoolInputValid )
615{
616 // Test with true value
617 DRC_RE_BOOL_INPUT_CONSTRAINT_DATA dataTrue( 0, 0, true, "BoolRuleTrue" );
618
619 VALIDATION_RESULT resultTrue = dataTrue.Validate();
620
621 BOOST_CHECK( resultTrue.isValid );
622 BOOST_CHECK( resultTrue.errors.empty() );
623
624 // Test with false value
625 DRC_RE_BOOL_INPUT_CONSTRAINT_DATA dataFalse( 0, 0, false, "BoolRuleFalse" );
626
627 VALIDATION_RESULT resultFalse = dataFalse.Validate();
628
629 BOOST_CHECK( resultFalse.isValid );
630 BOOST_CHECK( resultFalse.errors.empty() );
631}
632
633BOOST_AUTO_TEST_CASE( ValidateMinTxtHtThValid )
634{
635 // Valid: both text height and thickness are positive
636 DRC_RE_MINIMUM_TEXT_HEIGHT_THICKNESS_CONSTRAINT_DATA data( 0, 0, "ValidTextRule", 1.0, 0.15 );
637
639
640 BOOST_CHECK( result.isValid );
641 BOOST_CHECK( result.errors.empty() );
642}
643
644BOOST_AUTO_TEST_CASE( ValidateMinTxtHtThInvalid )
645{
646 // Invalid: negative text height
647 DRC_RE_MINIMUM_TEXT_HEIGHT_THICKNESS_CONSTRAINT_DATA data1( 0, 0, "NegativeHeightRule", -1.0, 0.15 );
648
649 VALIDATION_RESULT result1 = data1.Validate();
650
651 BOOST_CHECK( !result1.isValid );
652 BOOST_CHECK( !result1.errors.empty() );
653
654 bool foundHeightError = false;
655 for( const auto& error : result1.errors )
656 {
657 if( error.find( "Minimum Text Height must be greater than 0" ) != std::string::npos )
658 foundHeightError = true;
659 }
660 BOOST_CHECK( foundHeightError );
661
662 // Invalid: negative text thickness
663 DRC_RE_MINIMUM_TEXT_HEIGHT_THICKNESS_CONSTRAINT_DATA data2( 0, 0, "NegativeThicknessRule", 1.0, -0.15 );
664
665 VALIDATION_RESULT result2 = data2.Validate();
666
667 BOOST_CHECK( !result2.isValid );
668
669 bool foundThicknessError = false;
670 for( const auto& error : result2.errors )
671 {
672 if( error.find( "Minimum Text Thickness must be greater than 0" ) != std::string::npos )
673 foundThicknessError = true;
674 }
675 BOOST_CHECK( foundThicknessError );
676
677 // Invalid: both zero (default values)
679
680 VALIDATION_RESULT result3 = data3.Validate();
681
682 BOOST_CHECK( !result3.isValid );
683 BOOST_CHECK_EQUAL( result3.errors.size(), 2 );
684}
685
686BOOST_AUTO_TEST_CASE( ValidateCustomRuleValid )
687{
688 DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA data( 0, 0, "ValidCustomRule" );
689 data.SetRuleText( "(constraint clearance (min 0.2mm))" );
690
692
693 BOOST_CHECK( result.isValid );
694 BOOST_CHECK( result.errors.empty() );
695}
696
697BOOST_AUTO_TEST_CASE( ValidateCustomRuleInvalid )
698{
699 DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA data( 0, 0, "InvalidCustomRule" );
700 data.SetRuleText( "" );
701
703
704 BOOST_CHECK( !result.isValid );
705 BOOST_CHECK( !result.errors.empty() );
706
707 // Should have error about empty rule text
708 bool foundEmptyError = false;
709 for( const auto& error : result.errors )
710 {
711 if( error.find( "Rule text cannot be empty" ) != std::string::npos )
712 foundEmptyError = true;
713 }
714 BOOST_CHECK( foundEmptyError );
715}
716
717
718// ============================================================================
719// Panel Matcher Tests (Phase 5.1)
720// ============================================================================
721
722BOOST_AUTO_TEST_CASE( PanelMatcherExactMatchViaStyle )
723{
724 // Test: Rule with via_diameter + hole_size → VIA_STYLE panel
725 DRC_RULE rule( "ViaStyleRule" );
726
728 viaDia.Value().SetMin( 500000 );
729 viaDia.Value().SetOpt( 600000 );
730 viaDia.Value().SetMax( 800000 );
731 rule.AddConstraint( viaDia );
732
734 holeSize.Value().SetMin( 200000 );
735 holeSize.Value().SetOpt( 300000 );
736 holeSize.Value().SetMax( 400000 );
737 rule.AddConstraint( holeSize );
738
739 DRC_PANEL_MATCHER matcher;
740 std::vector<DRC_PANEL_MATCH> matches = matcher.MatchRule( rule );
741
742 BOOST_REQUIRE_EQUAL( matches.size(), 1 );
743 BOOST_CHECK_EQUAL( matches[0].panelType, VIA_STYLE );
744 BOOST_CHECK_EQUAL( matches[0].claimedConstraints.size(), 2 );
745 BOOST_CHECK( matches[0].claimedConstraints.count( VIA_DIAMETER_CONSTRAINT ) > 0 );
746 BOOST_CHECK( matches[0].claimedConstraints.count( HOLE_SIZE_CONSTRAINT ) > 0 );
747}
748
749BOOST_AUTO_TEST_CASE( PanelMatcherPartialMatchViaDiameter )
750{
751 // Test: Rule with only via_diameter → MINIMUM_VIA_DIAMETER panel
752 DRC_RULE rule( "ViaDiameterOnlyRule" );
753
755 viaDia.Value().SetMin( 500000 );
756 rule.AddConstraint( viaDia );
757
758 DRC_PANEL_MATCHER matcher;
759 std::vector<DRC_PANEL_MATCH> matches = matcher.MatchRule( rule );
760
761 BOOST_REQUIRE_EQUAL( matches.size(), 1 );
762 BOOST_CHECK_EQUAL( matches[0].panelType, MINIMUM_VIA_DIAMETER );
763 BOOST_CHECK_EQUAL( matches[0].claimedConstraints.size(), 1 );
764 BOOST_CHECK( matches[0].claimedConstraints.count( VIA_DIAMETER_CONSTRAINT ) > 0 );
765}
766
767BOOST_AUTO_TEST_CASE( PanelMatcherSplitRule )
768{
769 // Test: Rule with via_diameter + hole_size + clearance → 2 panels
770 DRC_RULE rule( "SplitRule" );
771
773 viaDia.Value().SetMin( 500000 );
774 rule.AddConstraint( viaDia );
775
777 holeSize.Value().SetMin( 200000 );
778 rule.AddConstraint( holeSize );
779
781 clearance.Value().SetMin( 150000 );
782 rule.AddConstraint( clearance );
783
784 DRC_PANEL_MATCHER matcher;
785 std::vector<DRC_PANEL_MATCH> matches = matcher.MatchRule( rule );
786
787 BOOST_REQUIRE_EQUAL( matches.size(), 2 );
788
789 // VIA_STYLE should claim via_diameter + hole_size
790 BOOST_CHECK_EQUAL( matches[0].panelType, VIA_STYLE );
791 BOOST_CHECK_EQUAL( matches[0].claimedConstraints.size(), 2 );
792
793 // MINIMUM_CLEARANCE should claim clearance
794 BOOST_CHECK_EQUAL( matches[1].panelType, MINIMUM_CLEARANCE );
795 BOOST_CHECK_EQUAL( matches[1].claimedConstraints.size(), 1 );
796 BOOST_CHECK( matches[1].claimedConstraints.count( CLEARANCE_CONSTRAINT ) > 0 );
797}
798
799BOOST_AUTO_TEST_CASE( PanelMatcherPriorityDiffPairOverRoutingWidth )
800{
801 // Test: Rule with track_width + diff_pair_gap → ROUTING_DIFF_PAIR (not ROUTING_WIDTH)
802 DRC_RULE rule( "DiffPairRule" );
803
805 trackWidth.Value().SetMin( 200000 );
806 trackWidth.Value().SetOpt( 250000 );
807 trackWidth.Value().SetMax( 300000 );
808 rule.AddConstraint( trackWidth );
809
811 diffGap.Value().SetMin( 100000 );
812 diffGap.Value().SetOpt( 150000 );
813 diffGap.Value().SetMax( 200000 );
814 rule.AddConstraint( diffGap );
815
816 DRC_PANEL_MATCHER matcher;
817 std::vector<DRC_PANEL_MATCH> matches = matcher.MatchRule( rule );
818
819 BOOST_REQUIRE_EQUAL( matches.size(), 1 );
820 BOOST_CHECK_EQUAL( matches[0].panelType, ROUTING_DIFF_PAIR );
821 BOOST_CHECK( matches[0].claimedConstraints.count( TRACK_WIDTH_CONSTRAINT ) > 0 );
822 BOOST_CHECK( matches[0].claimedConstraints.count( DIFF_PAIR_GAP_CONSTRAINT ) > 0 );
823}
824
825BOOST_AUTO_TEST_CASE( PanelMatcherDiffPairWithOptionalUncoupled )
826{
827 // Test: Rule with track_width + diff_pair_gap + uncoupled → ROUTING_DIFF_PAIR claims all three
828 DRC_RULE rule( "DiffPairWithUncoupledRule" );
829
831 trackWidth.Value().SetMin( 200000 );
832 rule.AddConstraint( trackWidth );
833
835 diffGap.Value().SetMin( 100000 );
836 rule.AddConstraint( diffGap );
837
839 uncoupled.Value().SetMax( 5000000 );
840 rule.AddConstraint( uncoupled );
841
842 DRC_PANEL_MATCHER matcher;
843 std::vector<DRC_PANEL_MATCH> matches = matcher.MatchRule( rule );
844
845 BOOST_REQUIRE_EQUAL( matches.size(), 1 );
846 BOOST_CHECK_EQUAL( matches[0].panelType, ROUTING_DIFF_PAIR );
847 BOOST_CHECK_EQUAL( matches[0].claimedConstraints.size(), 3 );
848 BOOST_CHECK( matches[0].claimedConstraints.count( TRACK_WIDTH_CONSTRAINT ) > 0 );
849 BOOST_CHECK( matches[0].claimedConstraints.count( DIFF_PAIR_GAP_CONSTRAINT ) > 0 );
850 BOOST_CHECK( matches[0].claimedConstraints.count( MAX_UNCOUPLED_CONSTRAINT ) > 0 );
851}
852
853BOOST_AUTO_TEST_CASE( PanelMatcherTrackWidthOnly )
854{
855 // Test: Rule with only track_width → ROUTING_WIDTH (not ROUTING_DIFF_PAIR)
856 DRC_RULE rule( "TrackWidthOnlyRule" );
857
859 trackWidth.Value().SetMin( 200000 );
860 trackWidth.Value().SetOpt( 250000 );
861 trackWidth.Value().SetMax( 300000 );
862 rule.AddConstraint( trackWidth );
863
864 DRC_PANEL_MATCHER matcher;
865 std::vector<DRC_PANEL_MATCH> matches = matcher.MatchRule( rule );
866
867 BOOST_REQUIRE_EQUAL( matches.size(), 1 );
868 BOOST_CHECK_EQUAL( matches[0].panelType, ROUTING_WIDTH );
869 BOOST_CHECK( matches[0].claimedConstraints.count( TRACK_WIDTH_CONSTRAINT ) > 0 );
870}
871
872BOOST_AUTO_TEST_CASE( PanelMatcherTextHeightAndThickness )
873{
874 // Test: Rule with text_height + text_thickness → MINIMUM_TEXT_HEIGHT_AND_THICKNESS
875 DRC_RULE rule( "TextRule" );
876
878 textHeight.Value().SetMin( 1000000 );
879 rule.AddConstraint( textHeight );
880
882 textThickness.Value().SetMin( 150000 );
883 rule.AddConstraint( textThickness );
884
885 DRC_PANEL_MATCHER matcher;
886 std::vector<DRC_PANEL_MATCH> matches = matcher.MatchRule( rule );
887
888 BOOST_REQUIRE_EQUAL( matches.size(), 1 );
890 BOOST_CHECK_EQUAL( matches[0].claimedConstraints.size(), 2 );
891}
892
893BOOST_AUTO_TEST_CASE( PanelMatcherLengthConstraint )
894{
895 // Test: Rule with length constraint matches ABSOLUTE_LENGTH panel
896 DRC_RULE rule( "LengthRule" );
897
899 length.Value().SetMin( 10000000 );
900 length.Value().SetOpt( 30000000 );
901 length.Value().SetMax( 50000000 );
902 rule.AddConstraint( length );
903
904 DRC_PANEL_MATCHER matcher;
905 std::vector<DRC_PANEL_MATCH> matches = matcher.MatchRule( rule );
906
907 BOOST_REQUIRE_EQUAL( matches.size(), 1 );
908 BOOST_CHECK_EQUAL( matches[0].panelType, ABSOLUTE_LENGTH );
909 BOOST_CHECK( matches[0].claimedConstraints.count( LENGTH_CONSTRAINT ) > 0 );
910}
911
912BOOST_AUTO_TEST_CASE( PanelMatcherLengthAndSkewConstraint )
913{
914 // Rule with length + skew should match MATCHED_LENGTH_DIFF_PAIR, not ABSOLUTE_LENGTH
915 DRC_RULE rule( "MatchedLengthRule" );
916
918 length.Value().SetMin( 10000000 );
919 length.Value().SetOpt( 30000000 );
920 length.Value().SetMax( 50000000 );
921 rule.AddConstraint( length );
922
924 skew.Value().SetMax( 1000000 );
925 rule.AddConstraint( skew );
926
927 DRC_PANEL_MATCHER matcher;
928 std::vector<DRC_PANEL_MATCH> matches = matcher.MatchRule( rule );
929
930 BOOST_REQUIRE_EQUAL( matches.size(), 1 );
931 BOOST_CHECK_EQUAL( matches[0].panelType, MATCHED_LENGTH_DIFF_PAIR );
932 BOOST_CHECK( matches[0].claimedConstraints.count( LENGTH_CONSTRAINT ) > 0 );
933 BOOST_CHECK( matches[0].claimedConstraints.count( SKEW_CONSTRAINT ) > 0 );
934}
935
936BOOST_AUTO_TEST_CASE( PanelMatcherEmptyRule )
937{
938 // Test: Rule with no constraints → no matches
939 DRC_RULE rule( "EmptyRule" );
940
941 DRC_PANEL_MATCHER matcher;
942 std::vector<DRC_PANEL_MATCH> matches = matcher.MatchRule( rule );
943
944 BOOST_CHECK_EQUAL( matches.size(), 0 );
945}
946
947BOOST_AUTO_TEST_CASE( PanelMatcherCanPanelLoad )
948{
949 DRC_PANEL_MATCHER matcher;
950
951 // VIA_STYLE can load via_diameter + hole_size
952 std::set<DRC_CONSTRAINT_T> viaStyleConstraints = { VIA_DIAMETER_CONSTRAINT, HOLE_SIZE_CONSTRAINT };
953 BOOST_CHECK( matcher.CanPanelLoad( VIA_STYLE, viaStyleConstraints ) );
954
955 // VIA_STYLE cannot load clearance
956 std::set<DRC_CONSTRAINT_T> clearanceConstraint = { CLEARANCE_CONSTRAINT };
957 BOOST_CHECK( !matcher.CanPanelLoad( VIA_STYLE, clearanceConstraint ) );
958
959 // CUSTOM_RULE can load anything
960 BOOST_CHECK( matcher.CanPanelLoad( CUSTOM_RULE, viaStyleConstraints ) );
961 BOOST_CHECK( matcher.CanPanelLoad( CUSTOM_RULE, clearanceConstraint ) );
962}
963
976
977
978// ============================================================================
979// Rule Loader Tests (Phase 5.1)
980// ============================================================================
981
982BOOST_AUTO_TEST_CASE( RuleLoaderViaStyleFromText )
983{
984 // Test: Load a via style rule from text and verify values
985 wxString ruleText =
986 "(version 1)\n"
987 "(rule \"Via Test\"\n"
988 " (constraint via_diameter (min 0.5mm) (opt 0.6mm) (max 0.8mm))\n"
989 " (constraint hole_size (min 0.2mm) (opt 0.3mm) (max 0.4mm)))";
990
991 DRC_RULE_LOADER loader;
992 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
993
994 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
995 BOOST_CHECK_EQUAL( entries[0].panelType, VIA_STYLE );
996 BOOST_CHECK_EQUAL( entries[0].ruleName, "Via Test" );
997
998 auto viaData = std::dynamic_pointer_cast<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>( entries[0].constraintData );
999 BOOST_REQUIRE( viaData );
1000
1001 BOOST_CHECK_CLOSE( viaData->GetMinViaDiameter(), 0.5, 0.0001 );
1002 BOOST_CHECK_CLOSE( viaData->GetMaxViaDiameter(), 0.8, 0.0001 );
1003 BOOST_CHECK_CLOSE( viaData->GetMinViaHoleSize(), 0.2, 0.0001 );
1004 BOOST_CHECK_CLOSE( viaData->GetMaxViaHoleSize(), 0.4, 0.0001 );
1005}
1006
1007BOOST_AUTO_TEST_CASE( RuleLoaderRoutingWidthFromText )
1008{
1009 // Test: Load a routing width rule from text
1010 wxString ruleText =
1011 "(version 1)\n"
1012 "(rule \"Track Test\"\n"
1013 " (constraint track_width (min 0.2mm) (opt 0.25mm) (max 0.3mm)))";
1014
1015 DRC_RULE_LOADER loader;
1016 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1017
1018 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
1019 BOOST_CHECK_EQUAL( entries[0].panelType, ROUTING_WIDTH );
1020 BOOST_CHECK_EQUAL( entries[0].ruleName, "Track Test" );
1021
1022 auto trackData = std::dynamic_pointer_cast<DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA>( entries[0].constraintData );
1023 BOOST_REQUIRE( trackData );
1024
1025 BOOST_CHECK_CLOSE( trackData->GetOptWidth(), 0.25, 0.0001 );
1026 BOOST_CHECK_CLOSE( trackData->GetWidthTolerance(), 0.05, 0.0001 );
1027}
1028
1029BOOST_AUTO_TEST_CASE( RuleLoaderDiffPairFromText )
1030{
1031 // Test: Load a diff pair rule from text
1032 wxString ruleText =
1033 "(version 1)\n"
1034 "(rule \"Diff Pair Test\"\n"
1035 " (constraint track_width (min 0.2mm) (opt 0.25mm) (max 0.3mm))\n"
1036 " (constraint diff_pair_gap (min 0.1mm) (opt 0.15mm) (max 0.2mm))\n"
1037 " (constraint diff_pair_uncoupled (max 5mm)))";
1038
1039 DRC_RULE_LOADER loader;
1040 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1041
1042 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
1043 BOOST_CHECK_EQUAL( entries[0].panelType, ROUTING_DIFF_PAIR );
1044
1045 auto dpData = std::dynamic_pointer_cast<DRC_RE_ROUTING_DIFF_PAIR_CONSTRAINT_DATA>( entries[0].constraintData );
1046 BOOST_REQUIRE( dpData );
1047
1048 BOOST_CHECK_CLOSE( dpData->GetOptWidth(), 0.25, 0.0001 );
1049 BOOST_CHECK_CLOSE( dpData->GetWidthTolerance(), 0.05, 0.0001 );
1050 BOOST_CHECK_CLOSE( dpData->GetOptGap(), 0.15, 0.0001 );
1051 BOOST_CHECK_CLOSE( dpData->GetGapTolerance(), 0.05, 0.0001 );
1052 BOOST_CHECK_CLOSE( dpData->GetMaxUncoupledLength(), 5.0, 0.0001 );
1053}
1054
1055BOOST_AUTO_TEST_CASE( RuleLoaderSplitRuleFromText )
1056{
1057 // Test: Load a rule that splits into multiple panels
1058 wxString ruleText =
1059 "(version 1)\n"
1060 "(rule \"Split Test\"\n"
1061 " (constraint via_diameter (min 0.5mm))\n"
1062 " (constraint hole_size (min 0.2mm))\n"
1063 " (constraint clearance (min 0.15mm)))";
1064
1065 DRC_RULE_LOADER loader;
1066 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1067
1068 BOOST_REQUIRE_EQUAL( entries.size(), 2 );
1069
1070 // First entry should be VIA_STYLE
1071 BOOST_CHECK_EQUAL( entries[0].panelType, VIA_STYLE );
1072 BOOST_CHECK_EQUAL( entries[0].ruleName, "Split Test" );
1073
1074 // Second entry should be MINIMUM_CLEARANCE
1075 BOOST_CHECK_EQUAL( entries[1].panelType, MINIMUM_CLEARANCE );
1076 BOOST_CHECK_EQUAL( entries[1].ruleName, "Split Test" );
1077
1078 auto numericData = std::dynamic_pointer_cast<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>( entries[1].constraintData );
1079 BOOST_REQUIRE( numericData );
1080 BOOST_CHECK_CLOSE( numericData->GetNumericInputValue(), 0.15, 0.0001 );
1081}
1082
1083BOOST_AUTO_TEST_CASE( RuleLoaderVmeWrenClearanceUnderFpga )
1084{
1085 // Test: Load the clearance_under_fpga rule from vme-wren demo which should split
1086 // This rule has: clearance, hole_size, via_diameter
1087 // Should split into: VIA_STYLE (hole_size + via_diameter) + MINIMUM_CLEARANCE (clearance)
1088 wxString ruleText =
1089 "(version 1)\n"
1090 "(rule \"clearance_under_fpga\"\n"
1091 " (constraint clearance (min 0.1mm))\n"
1092 " (constraint hole_size (min 0.2mm))\n"
1093 " (constraint via_diameter (min 0.4mm))\n"
1094 " (condition \"A.intersectsArea('underFPGA') || A.intersectsArea('underDDR')\"))";
1095
1096 DRC_RULE_LOADER loader;
1097 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1098
1099 // Should produce 2 entries from the split
1100 BOOST_REQUIRE_EQUAL( entries.size(), 2 );
1101
1102 // First entry should be VIA_STYLE (claims via_diameter + hole_size at priority 90)
1103 BOOST_CHECK_EQUAL( entries[0].panelType, VIA_STYLE );
1104 BOOST_CHECK_EQUAL( entries[0].ruleName, "clearance_under_fpga" );
1105 BOOST_CHECK_EQUAL( entries[0].condition, "A.intersectsArea('underFPGA') || A.intersectsArea('underDDR')" );
1106
1107 // Second entry should be MINIMUM_CLEARANCE (claims clearance at priority 30)
1108 BOOST_CHECK_EQUAL( entries[1].panelType, MINIMUM_CLEARANCE );
1109 BOOST_CHECK_EQUAL( entries[1].ruleName, "clearance_under_fpga" );
1110 BOOST_CHECK_EQUAL( entries[1].condition, "A.intersectsArea('underFPGA') || A.intersectsArea('underDDR')" );
1111
1112 // Verify VIA_STYLE has correct values
1113 auto viaData = std::dynamic_pointer_cast<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>( entries[0].constraintData );
1114 BOOST_REQUIRE( viaData );
1115 BOOST_CHECK_CLOSE( viaData->GetMinViaDiameter(), 0.4, 0.0001 );
1116 BOOST_CHECK_CLOSE( viaData->GetMinViaHoleSize(), 0.2, 0.0001 );
1117
1118 // Verify MINIMUM_CLEARANCE has correct value
1119 auto clearanceData = std::dynamic_pointer_cast<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>( entries[1].constraintData );
1120 BOOST_REQUIRE( clearanceData );
1121 BOOST_CHECK_CLOSE( clearanceData->GetNumericInputValue(), 0.1, 0.0001 );
1122}
1123
1124BOOST_AUTO_TEST_CASE( RuleLoaderTextHeightThicknessFromText )
1125{
1126 // Test: Load text height and thickness rule
1127 wxString ruleText =
1128 "(version 1)\n"
1129 "(rule \"Text Test\"\n"
1130 " (constraint text_height (min 1mm))\n"
1131 " (constraint text_thickness (min 0.15mm)))";
1132
1133 DRC_RULE_LOADER loader;
1134 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1135
1136 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
1138
1139 auto textData = std::dynamic_pointer_cast<DRC_RE_MINIMUM_TEXT_HEIGHT_THICKNESS_CONSTRAINT_DATA>( entries[0].constraintData );
1140 BOOST_REQUIRE( textData );
1141
1142 BOOST_CHECK_CLOSE( textData->GetMinTextHeight(), 1.0, 0.0001 );
1143 BOOST_CHECK_CLOSE( textData->GetMinTextThickness(), 0.15, 0.0001 );
1144}
1145
1146BOOST_AUTO_TEST_CASE( RuleLoaderAbsoluteLengthFromText )
1147{
1148 // Test: Load absolute length rule with min/opt/max
1149 wxString ruleText =
1150 "(version 1)\n"
1151 "(rule \"Length Test\"\n"
1152 " (constraint length (min 10mm) (opt 30mm) (max 50mm)))";
1153
1154 DRC_RULE_LOADER loader;
1155 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1156
1157 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
1158 BOOST_CHECK_EQUAL( entries[0].panelType, ABSOLUTE_LENGTH );
1159 BOOST_CHECK_EQUAL( entries[0].ruleName, "Length Test" );
1160
1161 auto lengthData = dynamic_pointer_cast<DRC_RE_ABSOLUTE_LENGTH_TWO_CONSTRAINT_DATA>( entries[0].constraintData );
1162 BOOST_REQUIRE( lengthData );
1163 BOOST_CHECK_CLOSE( lengthData->GetMinimumLength(), 10.0, 0.0001 );
1164 BOOST_CHECK_CLOSE( lengthData->GetOptimumLength(), 30.0, 0.0001 );
1165 BOOST_CHECK_CLOSE( lengthData->GetMaximumLength(), 50.0, 0.0001 );
1166}
1167
1168BOOST_AUTO_TEST_CASE( RuleLoaderMatchedLengthWithSkew )
1169{
1170 // Rule with length + skew should load as MATCHED_LENGTH_DIFF_PAIR and preserve skew
1171 wxString ruleText =
1172 "(version 1)\n"
1173 "(rule \"DP Length Match\"\n"
1174 " (constraint length (min 10mm) (opt 30mm) (max 50mm))\n"
1175 " (constraint skew (max 1mm)))";
1176
1177 DRC_RULE_LOADER loader;
1178 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1179
1180 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
1181 BOOST_CHECK_EQUAL( entries[0].panelType, MATCHED_LENGTH_DIFF_PAIR );
1182 BOOST_CHECK_EQUAL( entries[0].ruleName, "DP Length Match" );
1183
1184 auto matchedData =
1185 std::dynamic_pointer_cast<DRC_RE_MATCHED_LENGTH_DIFF_PAIR_CONSTRAINT_DATA>( entries[0].constraintData );
1186 BOOST_REQUIRE( matchedData );
1187
1188 BOOST_CHECK_CLOSE( matchedData->GetMinimumLength(), 10.0, 0.0001 );
1189 BOOST_CHECK_CLOSE( matchedData->GetOptimumLength(), 30.0, 0.0001 );
1190 BOOST_CHECK_CLOSE( matchedData->GetMaximumLength(), 50.0, 0.0001 );
1191 BOOST_CHECK_CLOSE( matchedData->GetMaxSkew(), 1.0, 0.0001 );
1192}
1193
1194BOOST_AUTO_TEST_CASE( RuleLoaderMatchedLengthRoundTrip )
1195{
1196 // Verify length+skew rule round-trips without losing skew
1197 wxString ruleText =
1198 "(version 1)\n"
1199 "(rule \"DP Match RT\"\n"
1200 " (constraint length (min 10mm) (opt 30mm) (max 50mm))\n"
1201 " (constraint skew (max 2.5mm)))";
1202
1203 DRC_RULE_LOADER loader;
1204 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1205
1206 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
1207 entries[0].wasEdited = true;
1208
1209 DRC_RULE_SAVER saver;
1210 wxString savedText = saver.GenerateRulesText( entries, nullptr );
1211
1212 BOOST_CHECK( savedText.Contains( "length" ) );
1213 BOOST_CHECK( savedText.Contains( "skew" ) );
1214 BOOST_CHECK( savedText.Contains( "2.5mm" ) );
1215
1216 std::vector<DRC_RE_LOADED_PANEL_ENTRY> reloaded = loader.LoadFromString( savedText );
1217
1218 BOOST_REQUIRE_EQUAL( reloaded.size(), 1 );
1219 BOOST_CHECK_EQUAL( reloaded[0].panelType, MATCHED_LENGTH_DIFF_PAIR );
1220
1221 auto reloadedData =
1222 std::dynamic_pointer_cast<DRC_RE_MATCHED_LENGTH_DIFF_PAIR_CONSTRAINT_DATA>( reloaded[0].constraintData );
1223 BOOST_REQUIRE( reloadedData );
1224
1225 BOOST_CHECK_CLOSE( reloadedData->GetMaxSkew(), 2.5, 0.0001 );
1226 BOOST_CHECK_CLOSE( reloadedData->GetMinimumLength(), 10.0, 0.0001 );
1227 BOOST_CHECK_CLOSE( reloadedData->GetOptimumLength(), 30.0, 0.0001 );
1228 BOOST_CHECK_CLOSE( reloadedData->GetMaximumLength(), 50.0, 0.0001 );
1229}
1230
1231BOOST_AUTO_TEST_CASE( RuleLoaderMatchedLengthWithinDiffPairs )
1232{
1233 // Rule with skew + within_diff_pairs should load and preserve the option
1234 wxString ruleText = "(version 1)\n"
1235 "(rule \"DP Skew Within\"\n"
1236 " (constraint length (min 10mm) (opt 30mm) (max 50mm))\n"
1237 " (constraint skew (max 1mm) (within_diff_pairs)))";
1238
1239 DRC_RULE_LOADER loader;
1240 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1241
1242 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
1243 BOOST_CHECK_EQUAL( entries[0].panelType, MATCHED_LENGTH_DIFF_PAIR );
1244
1245 auto matchedData =
1246 std::dynamic_pointer_cast<DRC_RE_MATCHED_LENGTH_DIFF_PAIR_CONSTRAINT_DATA>( entries[0].constraintData );
1247 BOOST_REQUIRE( matchedData );
1248
1249 BOOST_CHECK_CLOSE( matchedData->GetMaxSkew(), 1.0, 0.0001 );
1250 BOOST_CHECK( matchedData->GetWithinDiffPairs() );
1251
1252 // Round-trip: save and reload
1253 entries[0].wasEdited = true;
1254
1255 DRC_RULE_SAVER saver;
1256 wxString savedText = saver.GenerateRulesText( entries, nullptr );
1257
1258 BOOST_CHECK( savedText.Contains( "within_diff_pairs" ) );
1259
1260 std::vector<DRC_RE_LOADED_PANEL_ENTRY> reloaded = loader.LoadFromString( savedText );
1261
1262 BOOST_REQUIRE_EQUAL( reloaded.size(), 1 );
1263
1264 auto reloadedData =
1265 std::dynamic_pointer_cast<DRC_RE_MATCHED_LENGTH_DIFF_PAIR_CONSTRAINT_DATA>( reloaded[0].constraintData );
1266 BOOST_REQUIRE( reloadedData );
1267
1268 BOOST_CHECK_CLOSE( reloadedData->GetMaxSkew(), 1.0, 0.0001 );
1269 BOOST_CHECK( reloadedData->GetWithinDiffPairs() );
1270}
1271
1272BOOST_AUTO_TEST_CASE( RuleLoaderMatchedLengthWithoutWithinDiffPairs )
1273{
1274 // Rule without within_diff_pairs should default to false
1275 wxString ruleText = "(version 1)\n"
1276 "(rule \"DP Skew Bus\"\n"
1277 " (constraint length (min 10mm) (opt 30mm) (max 50mm))\n"
1278 " (constraint skew (max 2mm)))";
1279
1280 DRC_RULE_LOADER loader;
1281 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1282
1283 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
1284
1285 auto matchedData =
1286 std::dynamic_pointer_cast<DRC_RE_MATCHED_LENGTH_DIFF_PAIR_CONSTRAINT_DATA>( entries[0].constraintData );
1287 BOOST_REQUIRE( matchedData );
1288
1289 BOOST_CHECK( !matchedData->GetWithinDiffPairs() );
1290
1291 // Round-trip should NOT contain within_diff_pairs
1292 entries[0].wasEdited = true;
1293
1294 DRC_RULE_SAVER saver;
1295 wxString savedText = saver.GenerateRulesText( entries, nullptr );
1296
1297 BOOST_CHECK( !savedText.Contains( "within_diff_pairs" ) );
1298}
1299
1300BOOST_AUTO_TEST_CASE( RuleLoaderWithCondition )
1301{
1302 // Test: Load rule with condition
1303 wxString ruleText =
1304 "(version 1)\n"
1305 "(rule \"Conditional Test\"\n"
1306 " (condition \"A.NetClass == 'Power'\")\n"
1307 " (constraint clearance (min 0.3mm)))";
1308
1309 DRC_RULE_LOADER loader;
1310 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1311
1312 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
1313 BOOST_CHECK_EQUAL( entries[0].ruleName, "Conditional Test" );
1314 BOOST_CHECK_EQUAL( entries[0].condition, "A.NetClass == 'Power'" );
1315 BOOST_CHECK_EQUAL( entries[0].constraintData->GetRuleCondition(), "A.NetClass == 'Power'" );
1316}
1317
1318BOOST_AUTO_TEST_CASE( RuleLoaderMultipleRules )
1319{
1320 // Test: Load multiple rules and verify order preservation
1321 wxString ruleText =
1322 "(version 1)\n"
1323 "(rule \"Rule A\"\n"
1324 " (constraint clearance (min 0.2mm)))\n"
1325 "(rule \"Rule B\"\n"
1326 " (constraint track_width (min 0.15mm)))\n"
1327 "(rule \"Rule C\"\n"
1328 " (constraint via_diameter (min 0.5mm))\n"
1329 " (constraint hole_size (min 0.3mm)))";
1330
1331 DRC_RULE_LOADER loader;
1332 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1333
1334 BOOST_REQUIRE_EQUAL( entries.size(), 3 );
1335
1336 BOOST_CHECK_EQUAL( entries[0].ruleName, "Rule A" );
1337 BOOST_CHECK_EQUAL( entries[0].panelType, MINIMUM_CLEARANCE );
1338
1339 BOOST_CHECK_EQUAL( entries[1].ruleName, "Rule B" );
1340 BOOST_CHECK_EQUAL( entries[1].panelType, ROUTING_WIDTH );
1341
1342 BOOST_CHECK_EQUAL( entries[2].ruleName, "Rule C" );
1343 BOOST_CHECK_EQUAL( entries[2].panelType, VIA_STYLE );
1344}
1345
1346BOOST_AUTO_TEST_CASE( RuleLoaderEmptyRule )
1347{
1348 // Test: Rule with no constraints creates custom rule fallback
1349 wxString ruleText =
1350 "(version 1)\n"
1351 "(rule \"Empty Rule\")";
1352
1353 DRC_RULE_LOADER loader;
1354 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1355
1356 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
1357 BOOST_CHECK_EQUAL( entries[0].panelType, CUSTOM_RULE );
1358 BOOST_CHECK_EQUAL( entries[0].ruleName, "Empty Rule" );
1359}
1360
1361BOOST_AUTO_TEST_CASE( RuleLoaderInvalidText )
1362{
1363 // Test: Invalid rule text returns empty vector
1364 wxString ruleText = "not valid rule text at all";
1365
1366 DRC_RULE_LOADER loader;
1367 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1368
1369 BOOST_CHECK_EQUAL( entries.size(), 0 );
1370}
1371
1372BOOST_AUTO_TEST_CASE( RuleLoaderNumericConstraints )
1373{
1374 // Test various numeric constraints map to correct panels
1375 wxString ruleText =
1376 "(version 1)\n"
1377 "(rule \"Edge Clearance\"\n"
1378 " (constraint edge_clearance (min 0.5mm)))\n"
1379 "(rule \"Hole Clearance\"\n"
1380 " (constraint hole_clearance (min 0.3mm)))\n"
1381 "(rule \"Courtyard\"\n"
1382 " (constraint courtyard_clearance (min 0.25mm)))";
1383
1384 DRC_RULE_LOADER loader;
1385 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1386
1387 BOOST_REQUIRE_EQUAL( entries.size(), 3 );
1388
1389 BOOST_CHECK_EQUAL( entries[0].panelType, COPPER_TO_EDGE_CLEARANCE );
1390 BOOST_CHECK_EQUAL( entries[1].panelType, COPPER_TO_HOLE_CLEARANCE );
1391 BOOST_CHECK_EQUAL( entries[2].panelType, COURTYARD_CLEARANCE );
1392}
1393
1394
1395// ============================================================================
1396// Rule Saver Tests (Phase 3)
1397// ============================================================================
1398
1399BOOST_AUTO_TEST_CASE( RuleSaverBasicGeneration )
1400{
1401 // Test: Basic rule text generation from panel entry
1404 entry.ruleName = "TestClearance";
1405 entry.wasEdited = true;
1406
1407 auto numericData = std::make_shared<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>();
1408 numericData->SetRuleName( "TestClearance" );
1409 numericData->SetConstraintCode( "clearance" );
1410 numericData->SetNumericInputValue( 0.2 );
1411 entry.constraintData = numericData;
1412
1413 DRC_RULE_SAVER saver;
1414 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
1415 wxString result = saver.GenerateRulesText( entries, nullptr );
1416
1417 BOOST_CHECK( result.Contains( "(version 2)" ) );
1418 BOOST_CHECK( result.Contains( "TestClearance" ) );
1419 BOOST_CHECK( result.Contains( "clearance" ) );
1420}
1421
1422BOOST_AUTO_TEST_CASE( RuleSaverRoundTripPreservation )
1423{
1424 // Test: Original text preserved when not edited
1427 entry.ruleName = "Preserved Rule";
1428 entry.originalRuleText = "(rule \"Preserved Rule\"\n\t(constraint clearance (min 0.5mm)))";
1429 entry.wasEdited = false;
1430
1431 auto numericData = std::make_shared<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>();
1432 numericData->SetRuleName( "Preserved Rule" );
1433 numericData->SetConstraintCode( "clearance" );
1434 numericData->SetNumericInputValue( 0.5 );
1435 entry.constraintData = numericData;
1436
1437 DRC_RULE_SAVER saver;
1438 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
1439 wxString result = saver.GenerateRulesText( entries, nullptr );
1440
1441 BOOST_CHECK( result.Contains( entry.originalRuleText ) );
1442}
1443
1444BOOST_AUTO_TEST_CASE( RuleSaverEditedRuleRegenerated )
1445{
1446 // Test: Edited rules regenerate from panel data
1449 entry.ruleName = "EditedRule";
1450 entry.originalRuleText = "(rule \"EditedRule\"\n\t(constraint clearance (min 0.5mm)))";
1451 entry.wasEdited = true;
1452
1453 auto numericData = std::make_shared<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>();
1454 numericData->SetRuleName( "EditedRule" );
1455 numericData->SetConstraintCode( "clearance" );
1456 numericData->SetNumericInputValue( 0.3 );
1457 entry.constraintData = numericData;
1458
1459 DRC_RULE_SAVER saver;
1460 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
1461 wxString result = saver.GenerateRulesText( entries, nullptr );
1462
1463 // Original text should NOT be preserved since wasEdited is true
1464 BOOST_CHECK( !result.Contains( "0.5mm" ) );
1465 BOOST_CHECK( result.Contains( "EditedRule" ) );
1466}
1467
1468BOOST_AUTO_TEST_CASE( RuleSaverViaStyleRule )
1469{
1470 // Test: Via style rule generation
1472 entry.panelType = VIA_STYLE;
1473 entry.ruleName = "ViaTest";
1474
1475 auto viaData = std::make_shared<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>();
1476 viaData->SetRuleName( "ViaTest" );
1477 viaData->SetMinViaDiameter( 0.5 );
1478 viaData->SetMaxViaDiameter( 0.8 );
1479 viaData->SetMinViaHoleSize( 0.2 );
1480 viaData->SetMaxViaHoleSize( 0.4 );
1481 entry.constraintData = viaData;
1482 entry.wasEdited = true;
1483
1484 DRC_RULE_SAVER saver;
1485 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
1486 wxString result = saver.GenerateRulesText( entries, nullptr );
1487
1488 BOOST_CHECK( result.Contains( "ViaTest" ) );
1489 BOOST_CHECK( result.Contains( "via_diameter" ) );
1490 BOOST_CHECK( result.Contains( "hole_size" ) );
1491}
1492
1493BOOST_AUTO_TEST_CASE( RuleSaverMultipleEntries )
1494{
1495 // Test: Multiple entries saved in order
1496 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries;
1497
1500 entry1.ruleName = "RuleA";
1501 auto data1 = std::make_shared<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>();
1502 data1->SetRuleName( "RuleA" );
1503 data1->SetConstraintCode( "clearance" );
1504 data1->SetNumericInputValue( 0.2 );
1505 entry1.constraintData = data1;
1506 entry1.wasEdited = true;
1507 entries.push_back( entry1 );
1508
1510 entry2.panelType = ROUTING_WIDTH;
1511 entry2.ruleName = "RuleB";
1512 auto data2 = std::make_shared<DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA>();
1513 data2->SetRuleName( "RuleB" );
1514 data2->SetOptWidth( 0.2 );
1515 data2->SetWidthTolerance( 0.1 );
1516 entry2.constraintData = data2;
1517 entry2.wasEdited = true;
1518 entries.push_back( entry2 );
1519
1520 DRC_RULE_SAVER saver;
1521 wxString result = saver.GenerateRulesText( entries, nullptr );
1522
1523 // Both rules should appear
1524 BOOST_CHECK( result.Contains( "RuleA" ) );
1525 BOOST_CHECK( result.Contains( "RuleB" ) );
1526
1527 // Check order: RuleA should appear before RuleB
1528 size_t posA = result.Find( "RuleA" );
1529 size_t posB = result.Find( "RuleB" );
1530 BOOST_CHECK( posA < posB );
1531}
1532
1533BOOST_AUTO_TEST_CASE( RuleSaverWithCondition )
1534{
1535 // Test: Rule with condition
1538 entry.ruleName = "ConditionalRule";
1539 entry.condition = "A.NetClass == 'Power'";
1540
1541 auto numericData = std::make_shared<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>();
1542 numericData->SetRuleName( "ConditionalRule" );
1543 numericData->SetConstraintCode( "clearance" );
1544 numericData->SetRuleCondition( "A.NetClass == 'Power'" );
1545 numericData->SetNumericInputValue( 0.3 );
1546 entry.constraintData = numericData;
1547 entry.wasEdited = true;
1548
1549 DRC_RULE_SAVER saver;
1550 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
1551 wxString result = saver.GenerateRulesText( entries, nullptr );
1552
1553 BOOST_CHECK( result.Contains( "ConditionalRule" ) );
1554 BOOST_CHECK( result.Contains( "condition" ) );
1555 BOOST_CHECK( result.Contains( "Power" ) );
1556}
1557
1558BOOST_AUTO_TEST_CASE( RuleSaverEmptyEntries )
1559{
1560 // Test: Empty entries vector
1561 DRC_RULE_SAVER saver;
1562 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries;
1563 wxString result = saver.GenerateRulesText( entries, nullptr );
1564
1565 BOOST_CHECK( result.Contains( "(version 2)" ) );
1566 // Should have version header but nothing else
1567 BOOST_CHECK_EQUAL( result.Trim(), "(version 2)" );
1568}
1569
1570BOOST_AUTO_TEST_CASE( RuleSaverNullConstraintData )
1571{
1572 // Test: Entry with null constraint data is skipped
1575 entry.ruleName = "Null Data Rule";
1576 entry.constraintData = nullptr;
1577 entry.wasEdited = true;
1578
1579 DRC_RULE_SAVER saver;
1580 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
1581 wxString result = saver.GenerateRulesText( entries, nullptr );
1582
1583 // Should have version header but rule text is skipped
1584 BOOST_CHECK( result.Contains( "(version 2)" ) );
1585 BOOST_CHECK( !result.Contains( "Null Data Rule" ) );
1586}
1587
1588BOOST_AUTO_TEST_CASE( RuleSaverLoadSaveRoundTrip )
1589{
1590 // Test: Full round-trip: Load → mark edited → Save → Load produces equivalent data
1591 wxString originalText =
1592 "(version 1)\n"
1593 "(rule \"RoundTripTest\"\n"
1594 " (constraint clearance (min 0.25mm)))";
1595
1596 DRC_RULE_LOADER loader;
1597 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( originalText );
1598
1599 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
1600 BOOST_CHECK_EQUAL( entries[0].ruleName, "RoundTripTest" );
1601
1602 // Mark as edited so it regenerates from data (loader doesn't preserve exact original text)
1603 entries[0].wasEdited = true;
1604
1605 // Save
1606 DRC_RULE_SAVER saver;
1607 wxString savedText = saver.GenerateRulesText( entries, nullptr );
1608
1609 // Verify saved text is valid by reloading
1610 std::vector<DRC_RE_LOADED_PANEL_ENTRY> reloadedEntries = loader.LoadFromString( savedText );
1611
1612 BOOST_REQUIRE_EQUAL( reloadedEntries.size(), 1 );
1613 BOOST_CHECK_EQUAL( reloadedEntries[0].ruleName, "RoundTripTest" );
1614 BOOST_CHECK_EQUAL( reloadedEntries[0].panelType, entries[0].panelType );
1615}
1616
1617BOOST_AUTO_TEST_CASE( RuleSaverPreservesQuotedNameWithSpaces )
1618{
1619 // Issue 23852: editing a rule named like "Annular width: LEDs" must not rewrite
1620 // the name to Annular_width__LEDs. Names with whitespace or other characters
1621 // that require quoting must be emitted as a quoted string and round-trip intact.
1622 const wxString originalName = wxS( "Annular width: LEDs" );
1623
1626 entry.ruleName = originalName;
1627 entry.wasEdited = true;
1628
1629 auto numericData = std::make_shared<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>();
1630 numericData->SetRuleName( originalName );
1631 numericData->SetConstraintCode( "annular_width" );
1632 numericData->SetNumericInputValue( 0.085 );
1633 entry.constraintData = numericData;
1634
1635 DRC_RULE_SAVER saver;
1636 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
1637 wxString result = saver.GenerateRulesText( entries, nullptr );
1638
1639 // The saver must emit the name in quotes, not mangle it with underscores.
1640 BOOST_CHECK( result.Contains( wxS( "\"Annular width: LEDs\"" ) ) );
1641 BOOST_CHECK( !result.Contains( wxS( "Annular_width__LEDs" ) ) );
1642
1643 // Reload the saved text and verify the rule name survives unchanged.
1644 DRC_RULE_LOADER loader;
1645 std::vector<DRC_RE_LOADED_PANEL_ENTRY> reloaded = loader.LoadFromString( result );
1646
1647 BOOST_REQUIRE_EQUAL( reloaded.size(), 1 );
1648 BOOST_CHECK_EQUAL( reloaded[0].ruleName, originalName );
1649}
1650
1651
1652BOOST_AUTO_TEST_CASE( RuleSaverPreservesBareSymbolNameUnquoted )
1653{
1654 // A bare symbol rule name should still be emitted without quotes.
1657 entry.ruleName = wxS( "My_Bare_Rule-1.0" );
1658 entry.wasEdited = true;
1659
1660 auto numericData = std::make_shared<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>();
1661 numericData->SetRuleName( entry.ruleName );
1662 numericData->SetConstraintCode( "clearance" );
1663 numericData->SetNumericInputValue( 0.2 );
1664 entry.constraintData = numericData;
1665
1666 DRC_RULE_SAVER saver;
1667 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
1668 wxString result = saver.GenerateRulesText( entries, nullptr );
1669
1670 BOOST_CHECK( result.Contains( wxS( "(rule My_Bare_Rule-1.0\n" ) ) );
1671 BOOST_CHECK( !result.Contains( wxS( "\"My_Bare_Rule-1.0\"" ) ) );
1672}
1673
1674
1675BOOST_AUTO_TEST_CASE( RuleSaverQuotesNameStartingWithDigit )
1676{
1677 // Names starting with a digit must be quoted so S-expression parsers treat them
1678 // as a symbol rather than a numeric literal.
1681 entry.ruleName = wxS( "3.3V_Power" );
1682 entry.wasEdited = true;
1683
1684 auto numericData = std::make_shared<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>();
1685 numericData->SetRuleName( entry.ruleName );
1686 numericData->SetConstraintCode( "clearance" );
1687 numericData->SetNumericInputValue( 0.2 );
1688 entry.constraintData = numericData;
1689
1690 DRC_RULE_SAVER saver;
1691 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
1692 wxString result = saver.GenerateRulesText( entries, nullptr );
1693
1694 BOOST_CHECK( result.Contains( wxS( "\"3.3V_Power\"" ) ) );
1695
1696 DRC_RULE_LOADER loader;
1697 std::vector<DRC_RE_LOADED_PANEL_ENTRY> reloaded = loader.LoadFromString( result );
1698 BOOST_REQUIRE_EQUAL( reloaded.size(), 1 );
1699 BOOST_CHECK_EQUAL( reloaded[0].ruleName, "3.3V_Power" );
1700}
1701
1702
1703BOOST_AUTO_TEST_CASE( RuleSaverDiffPairRule )
1704{
1705 // Test: Diff pair rule generation
1708 entry.ruleName = "DiffPairTest";
1709
1710 auto dpData = std::make_shared<DRC_RE_ROUTING_DIFF_PAIR_CONSTRAINT_DATA>();
1711 dpData->SetRuleName( "DiffPairTest" );
1712 dpData->SetOptWidth( 0.25 );
1713 dpData->SetWidthTolerance( 0.05 );
1714 dpData->SetOptGap( 0.15 );
1715 dpData->SetGapTolerance( 0.05 );
1716 dpData->SetMaxUncoupledLength( 5.0 );
1717 entry.constraintData = dpData;
1718 entry.wasEdited = true;
1719
1720 DRC_RULE_SAVER saver;
1721 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
1722 wxString result = saver.GenerateRulesText( entries, nullptr );
1723
1724 BOOST_CHECK( result.Contains( "DiffPairTest" ) );
1725 BOOST_CHECK( result.Contains( "track_width" ) );
1726 BOOST_CHECK( result.Contains( "diff_pair_gap" ) );
1727}
1728
1729
1730// ============================================================================
1731// Integration Tests (Phase 5.2)
1732// ============================================================================
1733
1734BOOST_AUTO_TEST_CASE( IntegrationLoadHoleClearanceRules )
1735{
1736 // Test: Load issue6879.kicad_dru which has hole_clearance and hole constraints
1737 wxString ruleText =
1738 "(version 1)\n"
1739 "(rule \"PTH to Track Clearance\"\n"
1740 " (constraint hole_clearance (min 1.0mm))\n"
1741 " (condition \"A.Type == 'Pad' && A.Pad_Type == 'Through-hole' && B.Type =='Track'\"))\n"
1742 "(rule \"Max Drill Hole Size Mechanical\"\n"
1743 " (constraint hole (max 2.0mm))\n"
1744 " (condition \"A.Type == 'Pad'\"))";
1745
1746 DRC_RULE_LOADER loader;
1747 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1748
1749 BOOST_REQUIRE_GE( entries.size(), 2 );
1750
1751 // First rule should be COPPER_TO_HOLE_CLEARANCE
1752 BOOST_CHECK_EQUAL( entries[0].panelType, COPPER_TO_HOLE_CLEARANCE );
1753 BOOST_CHECK_EQUAL( entries[0].ruleName, "PTH to Track Clearance" );
1754 BOOST_CHECK( !entries[0].condition.IsEmpty() );
1755
1756 // Second rule should be HOLE_SIZE
1757 BOOST_CHECK_EQUAL( entries[1].panelType, HOLE_SIZE );
1758 BOOST_CHECK_EQUAL( entries[1].ruleName, "Max Drill Hole Size Mechanical" );
1759}
1760
1761BOOST_AUTO_TEST_CASE( IntegrationLoadEdgeClearanceWithSeverity )
1762{
1763 // Test: Load severities.kicad_dru which has severity clause
1764 wxString ruleText =
1765 "(version 1)\n"
1766 "(rule board_edge\n"
1767 " (constraint edge_clearance (min 1mm))\n"
1768 " (condition \"A.memberOf('board_edge')\")\n"
1769 " (severity ignore))";
1770
1771 DRC_RULE_LOADER loader;
1772 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1773
1774 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
1775 BOOST_CHECK_EQUAL( entries[0].panelType, COPPER_TO_EDGE_CLEARANCE );
1776 BOOST_CHECK_EQUAL( entries[0].ruleName, "board_edge" );
1777 BOOST_CHECK_EQUAL( entries[0].severity, RPT_SEVERITY_IGNORE );
1778}
1779
1780BOOST_AUTO_TEST_CASE( IntegrationLoadConnectionWidthRules )
1781{
1782 // Test: Load connection_width_rules.kicad_dru
1783 wxString ruleText =
1784 "(version 1)\n"
1785 "(rule high_current_netclass\n"
1786 " (constraint connection_width (min 0.16mm))\n"
1787 " (condition \"A.NetClass == 'High_current'\"))\n"
1788 "(rule high_current_area\n"
1789 " (constraint connection_width (min 0.16mm))\n"
1790 " (condition \"A.insideArea('high_current')\"))";
1791
1792 DRC_RULE_LOADER loader;
1793 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1794
1795 BOOST_REQUIRE_EQUAL( entries.size(), 2 );
1796
1797 // Both should be MINIMUM_CONNECTION_WIDTH
1798 BOOST_CHECK_EQUAL( entries[0].panelType, MINIMUM_CONNECTION_WIDTH );
1799 BOOST_CHECK_EQUAL( entries[1].panelType, MINIMUM_CONNECTION_WIDTH );
1800
1801 // Both should have conditions
1802 BOOST_CHECK( !entries[0].condition.IsEmpty() );
1803 BOOST_CHECK( !entries[1].condition.IsEmpty() );
1804}
1805
1806BOOST_AUTO_TEST_CASE( IntegrationSaveLoadRoundTripMultipleRules )
1807{
1808 // Test: Load multiple rules, save, reload - verify equivalent data
1809 // Use rule names without spaces to avoid sanitization differences
1810 wxString originalText =
1811 "(version 1)\n"
1812 "(rule ClearanceRule\n"
1813 " (constraint clearance (min 0.2mm))\n"
1814 " (condition \"A.NetClass == 'Power'\"))\n"
1815 "(rule TrackWidthRule\n"
1816 " (constraint track_width (min 0.15mm) (opt 0.2mm) (max 0.3mm)))";
1817
1818 DRC_RULE_LOADER loader;
1819 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( originalText );
1820
1821 BOOST_REQUIRE_EQUAL( entries.size(), 2 );
1822
1823 // Mark as edited to force regeneration
1824 for( auto& entry : entries )
1825 entry.wasEdited = true;
1826
1827 // Save
1828 DRC_RULE_SAVER saver;
1829 wxString savedText = saver.GenerateRulesText( entries, nullptr );
1830
1831 // Reload
1832 std::vector<DRC_RE_LOADED_PANEL_ENTRY> reloadedEntries = loader.LoadFromString( savedText );
1833
1834 BOOST_REQUIRE_EQUAL( reloadedEntries.size(), 2 );
1835
1836 // Verify panel types preserved correctly
1837 BOOST_CHECK_EQUAL( reloadedEntries[0].panelType, entries[0].panelType );
1838 BOOST_CHECK_EQUAL( reloadedEntries[1].panelType, entries[1].panelType );
1839
1840 // Verify rule names preserved
1841 BOOST_CHECK_EQUAL( reloadedEntries[0].ruleName, "ClearanceRule" );
1842 BOOST_CHECK_EQUAL( reloadedEntries[1].ruleName, "TrackWidthRule" );
1843}
1844
1845BOOST_AUTO_TEST_CASE( IntegrationSaveLoadViaStyleRoundTrip )
1846{
1847 // Test: Via style rules round-trip correctly
1848 wxString originalText =
1849 "(version 1)\n"
1850 "(rule \"Via Style\"\n"
1851 " (constraint via_diameter (min 0.5mm) (opt 0.6mm) (max 0.8mm))\n"
1852 " (constraint hole_size (min 0.2mm) (opt 0.3mm) (max 0.4mm)))";
1853
1854 DRC_RULE_LOADER loader;
1855 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( originalText );
1856
1857 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
1858 BOOST_CHECK_EQUAL( entries[0].panelType, VIA_STYLE );
1859
1860 auto viaData = std::dynamic_pointer_cast<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>( entries[0].constraintData );
1861 BOOST_REQUIRE( viaData );
1862
1863 double originalMinDia = viaData->GetMinViaDiameter();
1864 double originalMinHole = viaData->GetMinViaHoleSize();
1865
1866 // Mark as edited and save
1867 entries[0].wasEdited = true;
1868 DRC_RULE_SAVER saver;
1869 wxString savedText = saver.GenerateRulesText( entries, nullptr );
1870
1871 // Reload and verify values preserved
1872 std::vector<DRC_RE_LOADED_PANEL_ENTRY> reloadedEntries = loader.LoadFromString( savedText );
1873
1874 BOOST_REQUIRE_EQUAL( reloadedEntries.size(), 1 );
1875 BOOST_CHECK_EQUAL( reloadedEntries[0].panelType, VIA_STYLE );
1876
1877 auto reloadedViaData =
1878 std::dynamic_pointer_cast<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>( reloadedEntries[0].constraintData );
1879 BOOST_REQUIRE( reloadedViaData );
1880
1881 BOOST_CHECK_CLOSE( reloadedViaData->GetMinViaDiameter(), originalMinDia, 0.0001 );
1882 BOOST_CHECK_CLOSE( reloadedViaData->GetMinViaHoleSize(), originalMinHole, 0.0001 );
1883}
1884
1885BOOST_AUTO_TEST_CASE( IntegrationSplitRulePreservesOrder )
1886{
1887 // Test: A rule with multiple constraint types splits and preserves order
1888 wxString ruleText =
1889 "(version 1)\n"
1890 "(rule \"Rule A\"\n"
1891 " (constraint clearance (min 0.2mm)))\n"
1892 "(rule \"Rule B\"\n"
1893 " (constraint via_diameter (min 0.5mm))\n"
1894 " (constraint hole_size (min 0.2mm))\n"
1895 " (constraint track_width (min 0.15mm)))\n"
1896 "(rule \"Rule C\"\n"
1897 " (constraint edge_clearance (min 0.3mm)))";
1898
1899 DRC_RULE_LOADER loader;
1900 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
1901
1902 // Rule B splits into VIA_STYLE and ROUTING_WIDTH
1903 BOOST_REQUIRE_GE( entries.size(), 4 );
1904
1905 // Find entries by rule name
1906 int ruleACount = 0, ruleBCount = 0, ruleCCount = 0;
1907
1908 for( const auto& entry : entries )
1909 {
1910 if( entry.ruleName == "Rule A" )
1911 ruleACount++;
1912 else if( entry.ruleName == "Rule B" )
1913 ruleBCount++;
1914 else if( entry.ruleName == "Rule C" )
1915 ruleCCount++;
1916 }
1917
1918 BOOST_CHECK_EQUAL( ruleACount, 1 );
1919 BOOST_CHECK_GE( ruleBCount, 2 );
1920 BOOST_CHECK_EQUAL( ruleCCount, 1 );
1921
1922 // Verify original order: all Rule A entries before Rule B, Rule B before Rule C
1923 int lastAIndex = -1, firstBIndex = entries.size(), lastBIndex = -1, firstCIndex = entries.size();
1924
1925 for( size_t i = 0; i < entries.size(); i++ )
1926 {
1927 if( entries[i].ruleName == "Rule A" )
1928 lastAIndex = i;
1929 else if( entries[i].ruleName == "Rule B" )
1930 {
1931 if( (int)i < firstBIndex )
1932 firstBIndex = i;
1933
1934 lastBIndex = i;
1935 }
1936 else if( entries[i].ruleName == "Rule C" )
1937 {
1938 if( (int)i < firstCIndex )
1939 firstCIndex = i;
1940 }
1941 }
1942
1943 BOOST_CHECK( lastAIndex < firstBIndex );
1944 BOOST_CHECK( lastBIndex < firstCIndex );
1945}
1946
1947
1948// ============================================================================
1949// Condition Group Panel Tests
1950// ============================================================================
1951
1952BOOST_AUTO_TEST_CASE( ConditionGroupBuildSingleCondition )
1953{
1954 // Test: Single condition builds correctly
1955 // This tests the expression building logic used by DRC_RE_CONDITION_GROUP_PANEL
1956 wxString expr = "A.NetName == 'VCC'";
1957
1958 // When parsed and rebuilt, the result should be equivalent
1959 BOOST_CHECK( !expr.IsEmpty() );
1960 BOOST_CHECK( expr.Contains( "NetName" ) );
1961 BOOST_CHECK( expr.Contains( "VCC" ) );
1962}
1963
1964BOOST_AUTO_TEST_CASE( ConditionGroupTokenizeAndOperator )
1965{
1966 // Test: Tokenizing conditions with AND operator
1967 wxString expr = "A.NetName == 'VCC' && B.NetClass == 'Power'";
1968
1969 // Verify the expression contains expected operators
1970 BOOST_CHECK( expr.Contains( "&&" ) );
1971 BOOST_CHECK( expr.Contains( "NetName" ) );
1972 BOOST_CHECK( expr.Contains( "NetClass" ) );
1973}
1974
1975BOOST_AUTO_TEST_CASE( ConditionGroupTokenizeOrOperator )
1976{
1977 // Test: Tokenizing conditions with OR operator
1978 wxString expr = "A.intersectsArea('underFPGA') || A.intersectsArea('underDDR')";
1979
1980 // Verify the expression contains expected operators
1981 BOOST_CHECK( expr.Contains( "||" ) );
1982 BOOST_CHECK( expr.Contains( "intersectsArea" ) );
1983}
1984
1985BOOST_AUTO_TEST_CASE( ConditionGroupTokenizeAndNotOperator )
1986{
1987 // Test: Tokenizing conditions with AND NOT operator
1988 wxString expr = "A.NetClass == 'Power' && !A.intersectsArea('NoRouting')";
1989
1990 // Verify the expression contains expected operators and negation
1991 BOOST_CHECK( expr.Contains( "&&" ) );
1992 BOOST_CHECK( expr.Contains( "!" ) );
1993}
1994
1995BOOST_AUTO_TEST_CASE( ConditionGroupTokenizeComplexExpression )
1996{
1997 // Test: Complex expression from vme-wren.kicad_dru
1998 wxString expr = "A.intersectsArea('underFPGA') || A.intersectsArea('underDDR')";
1999
2000 // The expression should contain the expected patterns
2001 BOOST_CHECK( expr.Contains( "||" ) );
2002 BOOST_CHECK( expr.Contains( "underFPGA" ) );
2003 BOOST_CHECK( expr.Contains( "underDDR" ) );
2004
2005 // Count number of conditions (2 in this case)
2006 size_t orCount = 0;
2007 size_t pos = 0;
2008
2009 while( ( pos = expr.find( "||", pos ) ) != wxString::npos )
2010 {
2011 orCount++;
2012 pos += 2;
2013 }
2014
2015 BOOST_CHECK_EQUAL( orCount, 1 );
2016}
2017
2018
2019// ============================================================================
2020// DRC Engine Item Filtering Tests
2021// ============================================================================
2022
2023BOOST_AUTO_TEST_CASE( ItemFilterExcludesNetInfoAndGenerator )
2024{
2025 // Test: Verify the item type filter logic matches expected exclusions
2026 // This is a unit test for the switch statement in GetItemsMatchingCondition
2027
2028 std::vector<KICAD_T> excludedTypes = { PCB_NETINFO_T, PCB_GENERATOR_T, PCB_GROUP_T };
2029 std::vector<KICAD_T> includedTypes = { PCB_TRACE_T, PCB_VIA_T, PCB_PAD_T, PCB_FOOTPRINT_T,
2031
2032 for( KICAD_T type : excludedTypes )
2033 {
2034 switch( type )
2035 {
2036 case PCB_NETINFO_T:
2037 case PCB_GENERATOR_T:
2038 case PCB_GROUP_T:
2039 BOOST_CHECK( true ); // These should be excluded (continue in real code)
2040 break;
2041
2042 default:
2043 BOOST_FAIL( "Excluded type not handled in filter" );
2044 break;
2045 }
2046 }
2047
2048 for( KICAD_T type : includedTypes )
2049 {
2050 switch( type )
2051 {
2052 case PCB_NETINFO_T:
2053 case PCB_GENERATOR_T:
2054 case PCB_GROUP_T:
2055 BOOST_FAIL( "Included type incorrectly matched by filter" );
2056 break;
2057
2058 default:
2059 BOOST_CHECK( true ); // These should be included
2060 break;
2061 }
2062 }
2063}
2064
2065BOOST_AUTO_TEST_CASE( RuleSaverSilkToSoldermaskWithCondition )
2066{
2067 // Test: Silk-to-soldermask merges layer condition with user condition
2068 // into a single (condition ...) clause, not two separate ones.
2071 entry.ruleName = "SilkMaskTest";
2072 entry.condition = "A.NetClass == 'Power'";
2073 entry.wasEdited = true;
2074 entry.layerCondition = LSET( { F_SilkS } );
2075
2076 auto data = std::make_shared<DRC_RE_SILK_TO_SOLDERMASK_CLEARANCE_CONSTRAINT_DATA>();
2077 data->SetRuleName( "SilkMaskTest" );
2078 data->SetConstraintCode( "silk_clearance" );
2079 data->SetRuleCondition( "A.NetClass == 'Power'" );
2080 data->SetNumericInputValue( 0.15 );
2081 entry.constraintData = data;
2082
2083 DRC_RULE_SAVER saver;
2084 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
2085 wxString result = saver.GenerateRulesText( entries, nullptr );
2086
2087 // Should have a single merged condition, not two (condition ...) lines
2088 BOOST_CHECK( result.Contains( "L == 'F.Mask'" ) );
2089 BOOST_CHECK( result.Contains( "Power" ) );
2090 BOOST_CHECK( !result.Contains( "(layer" ) );
2091
2092 // Count occurrences of "(condition" — must be exactly one
2093 int condCount = 0;
2094 size_t pos = 0;
2095 while( ( pos = result.find( "(condition", pos ) ) != wxString::npos )
2096 {
2097 condCount++;
2098 pos++;
2099 }
2100 BOOST_CHECK_EQUAL( condCount, 1 );
2101}
2102
2103BOOST_AUTO_TEST_CASE( RuleSaverSilkToSoldermaskNoExtraCondition )
2104{
2105 // Test: Silk-to-soldermask without extra condition still produces
2106 // a condition clause, not a (layer ...) clause.
2109 entry.ruleName = "SilkMaskSimple";
2110 entry.wasEdited = true;
2111 entry.layerCondition = LSET( { B_SilkS } );
2112
2113 auto data = std::make_shared<DRC_RE_SILK_TO_SOLDERMASK_CLEARANCE_CONSTRAINT_DATA>();
2114 data->SetRuleName( "SilkMaskSimple" );
2115 data->SetConstraintCode( "silk_clearance" );
2116 data->SetNumericInputValue( 0.1 );
2117 entry.constraintData = data;
2118
2119 DRC_RULE_SAVER saver;
2120 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
2121 wxString result = saver.GenerateRulesText( entries, nullptr );
2122
2123 BOOST_CHECK( result.Contains( "L == 'B.Mask'" ) );
2124 BOOST_CHECK( result.Contains( "(condition" ) );
2125 BOOST_CHECK( !result.Contains( "(layer" ) );
2126}
2127
2128BOOST_AUTO_TEST_CASE( RuleSaverSilkToSilkBothLayers )
2129{
2130 // Silk-to-silk with both layers generates L == 'F.SilkS' || L == 'B.SilkS'
2133 entry.ruleName = "SilkSilkBoth";
2134 entry.wasEdited = true;
2135 entry.layerCondition = LSET( { F_SilkS, B_SilkS } );
2136
2137 auto data = std::make_shared<DRC_RE_SILK_TO_SILK_CLEARANCE_CONSTRAINT_DATA>();
2138 data->SetRuleName( "SilkSilkBoth" );
2139 data->SetConstraintCode( "silk_clearance" );
2140 data->SetNumericInputValue( 0.2 );
2141 entry.constraintData = data;
2142
2143 DRC_RULE_SAVER saver;
2144 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
2145 wxString result = saver.GenerateRulesText( entries, nullptr );
2146
2147 BOOST_CHECK( result.Contains( "L == 'F.SilkS' || L == 'B.SilkS'" ) );
2148 BOOST_CHECK( result.Contains( "(condition" ) );
2149 BOOST_CHECK( !result.Contains( "(layer" ) );
2150}
2151
2152BOOST_AUTO_TEST_CASE( RuleSaverSilkToSilkFrontOnly )
2153{
2154 // Silk-to-silk front only generates L == 'F.SilkS'
2157 entry.ruleName = "SilkSilkFront";
2158 entry.wasEdited = true;
2159 entry.layerCondition = LSET( { F_SilkS } );
2160
2161 auto data = std::make_shared<DRC_RE_SILK_TO_SILK_CLEARANCE_CONSTRAINT_DATA>();
2162 data->SetRuleName( "SilkSilkFront" );
2163 data->SetConstraintCode( "silk_clearance" );
2164 data->SetNumericInputValue( 0.2 );
2165 entry.constraintData = data;
2166
2167 DRC_RULE_SAVER saver;
2168 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
2169 wxString result = saver.GenerateRulesText( entries, nullptr );
2170
2171 BOOST_CHECK( result.Contains( "L == 'F.SilkS'" ) );
2172 BOOST_CHECK( !result.Contains( "B.SilkS" ) );
2173 BOOST_CHECK( result.Contains( "(condition" ) );
2174 BOOST_CHECK( !result.Contains( "(layer" ) );
2175}
2176
2177BOOST_AUTO_TEST_CASE( RuleSaverSilkToSilkWithCondition )
2178{
2179 // Silk-to-silk merges layer condition with user condition
2182 entry.ruleName = "SilkSilkCond";
2183 entry.condition = "A.NetClass == 'Signal'";
2184 entry.wasEdited = true;
2185 entry.layerCondition = LSET( { F_SilkS } );
2186
2187 auto data = std::make_shared<DRC_RE_SILK_TO_SILK_CLEARANCE_CONSTRAINT_DATA>();
2188 data->SetRuleName( "SilkSilkCond" );
2189 data->SetConstraintCode( "silk_clearance" );
2190 data->SetRuleCondition( "A.NetClass == 'Signal'" );
2191 data->SetNumericInputValue( 0.2 );
2192 entry.constraintData = data;
2193
2194 DRC_RULE_SAVER saver;
2195 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = { entry };
2196 wxString result = saver.GenerateRulesText( entries, nullptr );
2197
2198 BOOST_CHECK( result.Contains( "L == 'F.SilkS'" ) );
2199 BOOST_CHECK( result.Contains( "Signal" ) );
2200
2201 // Single merged condition
2202 int condCount = 0;
2203 size_t pos = 0;
2204 while( ( pos = result.find( "(condition", pos ) ) != wxString::npos )
2205 {
2206 condCount++;
2207 pos++;
2208 }
2209 BOOST_CHECK_EQUAL( condCount, 1 );
2210}
2211
2212BOOST_AUTO_TEST_CASE( ValidateViasUnderSmdValid )
2213{
2214 // Valid: at least one via type is selected
2215 DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA data1( 0, 0, "OnlyMicro", false, true, false, false );
2216 VALIDATION_RESULT result1 = data1.Validate();
2217 BOOST_CHECK( result1.isValid );
2218 BOOST_CHECK( result1.errors.empty() );
2219
2220 // Valid: all selected
2221 DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA data2( 0, 0, "AllVias", true, true, true, true );
2222 VALIDATION_RESULT result2 = data2.Validate();
2223 BOOST_CHECK( result2.isValid );
2224 BOOST_CHECK( result2.errors.empty() );
2225}
2226
2227BOOST_AUTO_TEST_CASE( ValidateViasUnderSmdInvalid )
2228{
2229 // Invalid: no via type selected
2230 DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA data( 0, 0, "NoneSelected", false, false, false, false );
2232 BOOST_CHECK( !result.isValid );
2233 BOOST_CHECK( !result.errors.empty() );
2234
2235 bool foundError = false;
2236 for( const auto& error : result.errors )
2237 {
2238 if( error.find( "At least one via type must be selected" ) != std::string::npos )
2239 foundError = true;
2240 }
2241 BOOST_CHECK( foundError );
2242}
2243
2244BOOST_AUTO_TEST_CASE( ViasUnderSmdClauseGeneration )
2245{
2247 ctx.ruleName = "TestRule";
2248 ctx.constraintCode = "disallow_via";
2249
2250 // All four selected - shorthand "via" used
2251 DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA allData( 0, 0, "AllRule", true, true, true, true );
2252 auto clauses = allData.GetConstraintClauses( ctx );
2253 BOOST_REQUIRE_EQUAL( clauses.size(), 1 );
2254 BOOST_CHECK( clauses[0].Contains( "disallow via" ) );
2255 BOOST_CHECK( !clauses[0].Contains( "through_via" ) );
2256
2257 // Only micro_via selected - specific token
2258 DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA microOnly( 0, 0, "MicroRule", false, true, false, false );
2259 clauses = microOnly.GetConstraintClauses( ctx );
2260 BOOST_REQUIRE_EQUAL( clauses.size(), 1 );
2261 BOOST_CHECK( clauses[0].Contains( "micro_via" ) );
2262 BOOST_CHECK( !clauses[0].Contains( "through_via" ) );
2263 BOOST_CHECK( !clauses[0].Contains( "blind_via" ) );
2264 BOOST_CHECK( !clauses[0].Contains( "buried_via" ) );
2265
2266 // Through + blind selected - both tokens, no shorthand
2267 DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA twoTypes( 0, 0, "TwoRule", true, false, true, false );
2268 clauses = twoTypes.GetConstraintClauses( ctx );
2269 BOOST_REQUIRE_EQUAL( clauses.size(), 1 );
2270 BOOST_CHECK( clauses[0].Contains( "through_via" ) );
2271 BOOST_CHECK( clauses[0].Contains( "blind_via" ) );
2272 BOOST_CHECK( !clauses[0].Contains( "micro_via" ) );
2273 BOOST_CHECK( !clauses[0].Contains( "buried_via" ) );
2274
2275 // None selected - empty
2276 DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA noneData( 0, 0, "NoneRule", false, false, false, false );
2277 clauses = noneData.GetConstraintClauses( ctx );
2278 BOOST_CHECK( clauses.empty() );
2279}
2280
2281BOOST_AUTO_TEST_CASE( IntegrationViasUnderSmdRoundTrip )
2282{
2283 // Load a rule with only micro_via disallowed
2284 wxString originalText = "(version 1)\n"
2285 "(rule \"ViasUnderSmdTest\"\n"
2286 " (constraint disallow micro_via)\n"
2287 " (condition \"A.Pad_Type == 'SMD' && A.insideArea('BGA*')\"))";
2288
2289 DRC_RULE_LOADER loader;
2290 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( originalText );
2291
2292 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
2293 BOOST_CHECK_EQUAL( entries[0].panelType, VIAS_UNDER_SMD );
2294
2295 auto viaData = std::dynamic_pointer_cast<DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA>( entries[0].constraintData );
2296 BOOST_REQUIRE( viaData );
2297
2298 // Only micro_via should be checked
2299 BOOST_CHECK( !viaData->GetDisallowThroughVias() );
2300 BOOST_CHECK( viaData->GetDisallowMicroVias() );
2301 BOOST_CHECK( !viaData->GetDisallowBlindVias() );
2302 BOOST_CHECK( !viaData->GetDisallowBuriedVias() );
2303
2304 // Mark as edited and save
2305 entries[0].wasEdited = true;
2306 DRC_RULE_SAVER saver;
2307 wxString savedText = saver.GenerateRulesText( entries, nullptr );
2308
2309 // Verify saved text preserves specific via type (not blanket "via")
2310 BOOST_CHECK( savedText.Contains( "micro_via" ) );
2311 BOOST_CHECK( !savedText.Contains( "through_via" ) );
2312 BOOST_CHECK( !savedText.Contains( "blind_via" ) );
2313 BOOST_CHECK( !savedText.Contains( "buried_via" ) );
2314
2315 // Reload and verify roundtrip
2316 std::vector<DRC_RE_LOADED_PANEL_ENTRY> reloadedEntries = loader.LoadFromString( savedText );
2317 BOOST_REQUIRE_EQUAL( reloadedEntries.size(), 1 );
2318
2319 auto reloadedData =
2320 std::dynamic_pointer_cast<DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA>( reloadedEntries[0].constraintData );
2321 BOOST_REQUIRE( reloadedData );
2322
2323 BOOST_CHECK( !reloadedData->GetDisallowThroughVias() );
2324 BOOST_CHECK( reloadedData->GetDisallowMicroVias() );
2325 BOOST_CHECK( !reloadedData->GetDisallowBlindVias() );
2326 BOOST_CHECK( !reloadedData->GetDisallowBuriedVias() );
2327
2328 // Test blanket "via" sets all four flags
2329 wxString blanketText = "(version 1)\n"
2330 "(rule \"BlanketVia\"\n"
2331 " (constraint disallow via)\n"
2332 " (condition \"A.Pad_Type == 'SMD' && A.insideArea('BGA*')\"))";
2333
2334 entries = loader.LoadFromString( blanketText );
2335 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
2336
2337 auto blanketData = std::dynamic_pointer_cast<DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA>( entries[0].constraintData );
2338 BOOST_REQUIRE( blanketData );
2339
2340 BOOST_CHECK( blanketData->GetDisallowThroughVias() );
2341 BOOST_CHECK( blanketData->GetDisallowMicroVias() );
2342 BOOST_CHECK( blanketData->GetDisallowBlindVias() );
2343 BOOST_CHECK( blanketData->GetDisallowBuriedVias() );
2344
2345 // Test multiple specific types roundtrip
2346 wxString multiText = "(version 1)\n"
2347 "(rule \"TwoVias\"\n"
2348 " (constraint disallow through_via blind_via)\n"
2349 " (condition \"A.Pad_Type == 'SMD' && A.insideArea('BGA*')\"))";
2350
2351 entries = loader.LoadFromString( multiText );
2352 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
2353
2354 auto multiData = std::dynamic_pointer_cast<DRC_RE_VIAS_UNDER_SMD_CONSTRAINT_DATA>( entries[0].constraintData );
2355 BOOST_REQUIRE( multiData );
2356
2357 BOOST_CHECK( multiData->GetDisallowThroughVias() );
2358 BOOST_CHECK( !multiData->GetDisallowMicroVias() );
2359 BOOST_CHECK( multiData->GetDisallowBlindVias() );
2360 BOOST_CHECK( !multiData->GetDisallowBuriedVias() );
2361
2362 entries[0].wasEdited = true;
2363 savedText = saver.GenerateRulesText( entries, nullptr );
2364
2365 BOOST_CHECK( savedText.Contains( "through_via" ) );
2366 BOOST_CHECK( savedText.Contains( "blind_via" ) );
2367 BOOST_CHECK( !savedText.Contains( "micro_via" ) );
2368 BOOST_CHECK( !savedText.Contains( "buried_via" ) );
2369}
2370
2371BOOST_AUTO_TEST_CASE( RuleLoaderNonStandardOrientationFallsBackToCustom )
2372{
2373 // Quoted name
2374 wxString ruleText = "(version 1)\n"
2375 "(rule \"Angled\"\n"
2376 " (constraint assertion \"A.Orientation == 46 deg\")\n"
2377 ")";
2378
2379 DRC_RULE_LOADER loader;
2380 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
2381
2382 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
2383 BOOST_CHECK_EQUAL( entries[0].panelType, CUSTOM_RULE );
2384
2385 auto customData = std::dynamic_pointer_cast<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>( entries[0].constraintData );
2386 BOOST_REQUIRE( customData );
2387 BOOST_CHECK( customData->GetRuleText().Contains( wxS( "46 deg" ) ) );
2388 BOOST_CHECK( !customData->GetRuleText().Contains( wxS( "Angled" ) ) );
2389
2390 // Unquoted name
2391 wxString ruleTextUnquoted = "(version 1)\n"
2392 "(rule Angled\n"
2393 " (constraint assertion \"A.Orientation == 46 deg\")\n"
2394 ")";
2395
2396 entries = loader.LoadFromString( ruleTextUnquoted );
2397
2398 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
2399 BOOST_CHECK_EQUAL( entries[0].panelType, CUSTOM_RULE );
2400
2401 auto customDataUnquoted =
2402 std::dynamic_pointer_cast<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>( entries[0].constraintData );
2403 BOOST_REQUIRE( customDataUnquoted );
2404 BOOST_CHECK( customDataUnquoted->GetRuleText().Contains( wxS( "46 deg" ) ) );
2405 BOOST_CHECK( !customDataUnquoted->GetRuleText().Contains( wxS( "Angled" ) ) );
2406}
2407
2408BOOST_AUTO_TEST_CASE( RuleLoaderStandardOrientationLoadsStructured )
2409{
2410 wxString ruleText = "(version 1)\n"
2411 "(rule \"Standard\"\n"
2412 " (constraint assertion \"A.Orientation == 0 deg || A.Orientation == 90 deg\")\n"
2413 ")";
2414
2415 DRC_RULE_LOADER loader;
2416 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
2417
2418 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
2419 BOOST_CHECK_EQUAL( entries[0].panelType, ALLOWED_ORIENTATION );
2420
2421 auto orientData =
2422 std::dynamic_pointer_cast<DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA>( entries[0].constraintData );
2423 BOOST_REQUIRE( orientData );
2424 BOOST_CHECK( orientData->GetIsZeroDegreesAllowed() );
2425 BOOST_CHECK( orientData->GetIsNinetyDegreesAllowed() );
2426 BOOST_CHECK( !orientData->GetIsOneEightyDegreesAllowed() );
2427 BOOST_CHECK( !orientData->GetIsAllDegreesAllowed() );
2428
2429 // Single-line variant
2430 wxString singleLine = "(version 1) (rule \"SingleLine\" (constraint assertion \"A.Orientation == 0 deg || "
2431 "A.Orientation == 90 deg\"))";
2432
2433 entries = loader.LoadFromString( singleLine );
2434
2435 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
2436 BOOST_CHECK_EQUAL( entries[0].panelType, ALLOWED_ORIENTATION );
2437
2438 auto singleLineData =
2439 std::dynamic_pointer_cast<DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA>( entries[0].constraintData );
2440 BOOST_REQUIRE( singleLineData );
2441 BOOST_CHECK( singleLineData->GetIsZeroDegreesAllowed() );
2442 BOOST_CHECK( singleLineData->GetIsNinetyDegreesAllowed() );
2443 BOOST_CHECK( !singleLineData->GetIsOneEightyDegreesAllowed() );
2444 BOOST_CHECK( !singleLineData->GetIsAllDegreesAllowed() );
2445}
2446
2447BOOST_AUTO_TEST_CASE( RuleLoaderAllowAllOrientationLoadsStructured )
2448{
2449 wxString ruleText = "(version 1)\n"
2450 "(rule \"AllAngles\"\n"
2451 " (constraint assertion \"A.Orientation == 0 deg || A.Orientation == 90 deg || "
2452 "A.Orientation == 180 deg || A.Orientation == 270 deg\")\n"
2453 ")";
2454
2455 DRC_RULE_LOADER loader;
2456 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
2457
2458 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
2459 BOOST_CHECK_EQUAL( entries[0].panelType, ALLOWED_ORIENTATION );
2460
2461 auto orientData =
2462 std::dynamic_pointer_cast<DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA>( entries[0].constraintData );
2463 BOOST_REQUIRE( orientData );
2464 BOOST_CHECK( orientData->GetIsAllDegreesAllowed() );
2465 BOOST_CHECK( orientData->GetIsZeroDegreesAllowed() );
2466 BOOST_CHECK( orientData->GetIsNinetyDegreesAllowed() );
2467 BOOST_CHECK( orientData->GetIsOneEightyDegreesAllowed() );
2468 BOOST_CHECK( orientData->GetIsTwoSeventyDegreesAllowed() );
2469}
2470
2471BOOST_AUTO_TEST_CASE( RuleLoaderPermittedLayersInnerLayerFallsBackToCustom )
2472{
2473 wxString ruleText = "(version 1)\n"
2474 "(rule \"InnerLayers\"\n"
2475 " (constraint assertion \"A.Layer == 'F.Cu' || A.Layer == 'In1.Cu'\")\n"
2476 ")";
2477
2478 DRC_RULE_LOADER loader;
2479 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
2480
2481 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
2482 BOOST_CHECK_EQUAL( entries[0].panelType, CUSTOM_RULE );
2483
2484 auto customData = std::dynamic_pointer_cast<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>( entries[0].constraintData );
2485 BOOST_REQUIRE( customData );
2486 BOOST_CHECK( customData->GetRuleText().Contains( wxS( "In1.Cu" ) ) );
2487}
2488
2489BOOST_AUTO_TEST_CASE( RuleLoaderPermittedLayersStandardLoadsStructured )
2490{
2491 wxString ruleText = "(version 1)\n"
2492 "(rule \"StandardLayers\"\n"
2493 " (constraint assertion \"A.Layer == 'F.Cu' || A.Layer == 'B.Cu'\")\n"
2494 ")";
2495
2496 DRC_RULE_LOADER loader;
2497 std::vector<DRC_RE_LOADED_PANEL_ENTRY> entries = loader.LoadFromString( ruleText );
2498
2499 BOOST_REQUIRE_EQUAL( entries.size(), 1 );
2500 BOOST_CHECK_EQUAL( entries[0].panelType, PERMITTED_LAYERS );
2501
2502 auto layerData = std::dynamic_pointer_cast<DRC_RE_PERMITTED_LAYERS_CONSTRAINT_DATA>( entries[0].constraintData );
2503 BOOST_REQUIRE( layerData );
2504 BOOST_CHECK( layerData->GetTopLayerEnabled() );
2505 BOOST_CHECK( layerData->GetBottomLayerEnabled() );
2506}
2507
MINOPTMAX< int > & Value()
Definition drc_rule.h:197
Maps DRC rule constraints to appropriate editor panels.
DRC_RULE_EDITOR_CONSTRAINT_NAME GetPanelForConstraint(DRC_CONSTRAINT_T aConstraintType)
Get the panel type for a single constraint.
bool CanPanelLoad(DRC_RULE_EDITOR_CONSTRAINT_NAME aPanel, const std::set< DRC_CONSTRAINT_T > &aConstraints)
Check if a specific panel type can load a set of constraints.
std::vector< DRC_PANEL_MATCH > MatchRule(const DRC_RULE &aRule)
Match a DRC rule to one or more panel types.
VALIDATION_RESULT Validate() const override
Validates the constraint data.
VALIDATION_RESULT Validate() const override
Validates the constraint data.
void SetConstraintCode(const wxString &aCode)
void SetRuleCondition(const wxString &aRuleCondition)
VALIDATION_RESULT Validate() const override
Validates the constraint data.
Simple constraint data object used by custom rules.
VALIDATION_RESULT Validate() const override
Validates the constraint data.
VALIDATION_RESULT Validate() const override
Validates the constraint data.
VALIDATION_RESULT Validate() const override
Validates the constraint data.
VALIDATION_RESULT Validate() const override
Validates the constraint data.
VALIDATION_RESULT Validate() const override
Validates the constraint data.
wxString GenerateRule(const RULE_GENERATION_CONTEXT &aContext) override
VALIDATION_RESULT Validate() const override
Validates the constraint data.
VALIDATION_RESULT Validate() const override
Validates the constraint data.
std::vector< wxString > GetConstraintClauses(const RULE_GENERATION_CONTEXT &aContext) const override
Returns just the constraint clauses without the rule wrapper.
VALIDATION_RESULT Validate() const override
Validates the constraint data.
wxString GenerateRule(const RULE_GENERATION_CONTEXT &aContext) override
static bool SaveRules(const wxString &aFilename, const std::vector< std::shared_ptr< DRC_RE_BASE_CONSTRAINT_DATA > > &aRules, const BOARD *aBoard)
static void RegisterRuleConverter(RuleConverter aConverter)
static std::vector< std::shared_ptr< DRC_RE_BASE_CONSTRAINT_DATA > > ParseRules(const wxString &aRules)
Loads DRC rules from .kicad_dru files and converts them to panel entries.
std::vector< DRC_RE_LOADED_PANEL_ENTRY > LoadFromString(const wxString &aRulesText)
Load rules from a text string.
Saves DRC panel entries back to .kicad_dru files.
wxString GenerateRulesText(const std::vector< DRC_RE_LOADED_PANEL_ENTRY > &aEntries, const BOARD *aBoard=nullptr)
Generate rule text from panel entries.
void AddConstraint(DRC_CONSTRAINT &aConstraint)
Definition drc_rule.cpp:61
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
void SetMin(T v)
Definition minoptmax.h:37
void SetOpt(T v)
Definition minoptmax.h:39
void SetMax(T v)
Definition minoptmax.h:38
wxString GetRuleName()
Get the name of the rule.
@ VIA_DIAMETER_CONSTRAINT
Definition drc_rule.h:72
@ DIFF_PAIR_GAP_CONSTRAINT
Definition drc_rule.h:78
@ TRACK_WIDTH_CONSTRAINT
Definition drc_rule.h:61
@ SILK_CLEARANCE_CONSTRAINT
Definition drc_rule.h:58
@ EDGE_CLEARANCE_CONSTRAINT
Definition drc_rule.h:55
@ TEXT_THICKNESS_CONSTRAINT
Definition drc_rule.h:60
@ LENGTH_CONSTRAINT
Definition drc_rule.h:73
@ CLEARANCE_CONSTRAINT
Definition drc_rule.h:51
@ MAX_UNCOUPLED_CONSTRAINT
Definition drc_rule.h:79
@ SKEW_CONSTRAINT
Definition drc_rule.h:77
@ HOLE_CLEARANCE_CONSTRAINT
Definition drc_rule.h:53
@ HOLE_SIZE_CONSTRAINT
Definition drc_rule.h:56
@ TEXT_HEIGHT_CONSTRAINT
Definition drc_rule.h:59
@ HOLE_TO_HOLE_CONSTRAINT
Definition drc_rule.h:54
@ ALLOWED_ORIENTATION
@ SILK_TO_SILK_CLEARANCE
@ ROUTING_DIFF_PAIR
@ SILK_TO_SOLDERMASK_CLEARANCE
@ COURTYARD_CLEARANCE
@ MINIMUM_CONNECTION_WIDTH
@ ABSOLUTE_LENGTH
@ MINIMUM_CLEARANCE
@ PERMITTED_LAYERS
@ MINIMUM_TEXT_HEIGHT_AND_THICKNESS
@ COPPER_TO_HOLE_CLEARANCE
@ HOLE_TO_HOLE_DISTANCE
@ ROUTING_WIDTH
@ MATCHED_LENGTH_DIFF_PAIR
@ COPPER_TO_EDGE_CLEARANCE
@ MINIMUM_VIA_DIAMETER
@ F_SilkS
Definition layer_ids.h:96
@ B_SilkS
Definition layer_ids.h:97
@ RPT_SEVERITY_IGNORE
Represents a rule loaded from a .kicad_dru file and mapped to a panel.
wxString ruleName
wxString originalRuleText
wxString condition
bool wasEdited
LSET layerCondition
std::shared_ptr< DRC_RE_BASE_CONSTRAINT_DATA > constraintData
DRC_RULE_EDITOR_CONSTRAINT_NAME panelType
Structure representing a node in a rule tree, collection of this used for building the rule tree.
std::shared_ptr< RULE_EDITOR_DATA_BASE > m_nodeData
Result of a validation operation.
std::vector< wxString > errors
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(RoundTripViaStyle)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
int clearance
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:71
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:81
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:84
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:90
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:104
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:101
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:85
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:79
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:80
@ PCB_NETINFO_T
class NETINFO_ITEM, a description of a net
Definition typeinfo.h:103
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:89