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