KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_rule.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20
21#include <board.h>
22#include <board_item.h>
23#include <api/api_enums.h>
24#include <api/board/board.pb.h>
25#include <api/board/board_rules.pb.h>
26#include <base_units.h>
27#include <string_utils.h>
28#include <drc/drc_rule.h>
30
31
32bool IsComponentClassSelector( const wxString& aToken )
33{
34 return aToken.Upper().StartsWith( wxT( "CLASS:" ) );
35}
36
37
39 m_Unary( false ),
41 m_ImplicitItem( nullptr ),
42 m_LayerCondition( LSET::AllLayersMask() ),
43 m_Condition( nullptr ),
46{
47}
48
49
50DRC_RULE::DRC_RULE( const wxString& aName ) :
51 m_Unary( false ),
53 m_ImplicitItem( nullptr ),
54 m_Name( aName ),
55 m_LayerCondition( LSET::AllLayersMask() ),
56 m_Condition( nullptr ),
59{
60}
61
62
64{
65 delete m_Condition;
66}
67
68
70{
71 aConstraint.SetParentRule( this );
72 m_Constraints.push_back( aConstraint );
73}
74
75
76std::optional<DRC_CONSTRAINT> DRC_RULE::FindConstraint( DRC_CONSTRAINT_T aType )
77{
79 {
80 if( c.m_Type == aType )
81 return c;
82 }
83
84 return std::optional<DRC_CONSTRAINT>();
85}
86
87
89{
90 // Classify by the items supplied to rule evaluation, not the geometry measured by the test.
91 switch( m_Type )
92 {
107 case SKEW_CONSTRAINT:
113 case HOLE_ASPECT_RATIO_CONSTRAINT: return true;
114
115 default:
116 return false;
117 }
118}
119
120
121wxString DRC_RULE::FormatRuleFromProto( const kiapi::board::CustomRule& aRule, wxString* aErrorText )
122{
123 if( aErrorText )
124 *aErrorText = wxEmptyString;
125
126 auto fail =
127 [&]( const wxString& aMessage ) -> wxString
128 {
129 if( aErrorText )
130 *aErrorText = aMessage;
131
132 return wxEmptyString;
133 };
134
135 auto escapeQuotedRuleText =
136 []( const wxString& aText ) -> wxString
137 {
138 wxString escaped = aText;
139 escaped.Replace( "\\", "\\\\" );
140 escaped.Replace( "\"", "\\\"" );
141 return escaped;
142 };
143
144 auto hasConstraintOption =
145 []( const kiapi::board::CustomRuleConstraint& aConstraint,
146 const kiapi::board::CustomRuleConstraintOption aOption ) -> bool
147 {
148 for( int optionValue : aConstraint.options() )
149 {
150 if( static_cast<kiapi::board::CustomRuleConstraintOption>( optionValue ) == aOption )
151 return true;
152 }
153
154 return false;
155 };
156
157 auto getConstraintToken =
158 []( const kiapi::board::CustomRuleConstraintType aType,
159 bool* aAddWithinDiffPairs ) -> std::optional<wxString>
160 {
161 if( aAddWithinDiffPairs )
162 *aAddWithinDiffPairs = false;
163
164 switch( aType )
165 {
166 case kiapi::board::CRCT_CLEARANCE: return wxS( "clearance" );
167 case kiapi::board::CRCT_CREEPAGE: return wxS( "creepage" );
168 case kiapi::board::CRCT_HOLE_CLEARANCE: return wxS( "hole_clearance" );
169 case kiapi::board::CRCT_HOLE_TO_HOLE: return wxS( "hole_to_hole" );
170 case kiapi::board::CRCT_EDGE_CLEARANCE: return wxS( "edge_clearance" );
171 case kiapi::board::CRCT_HOLE_SIZE: return wxS( "hole_size" );
172 case kiapi::board::CRCT_COURTYARD_CLEARANCE: return wxS( "courtyard_clearance" );
173 case kiapi::board::CRCT_SILK_CLEARANCE: return wxS( "silk_clearance" );
174 case kiapi::board::CRCT_TEXT_HEIGHT: return wxS( "text_height" );
175 case kiapi::board::CRCT_TEXT_THICKNESS: return wxS( "text_thickness" );
176 case kiapi::board::CRCT_TRACK_WIDTH: return wxS( "track_width" );
177 case kiapi::board::CRCT_TRACK_SEGMENT_LENGTH: return wxS( "track_segment_length" );
178 case kiapi::board::CRCT_ANNULAR_WIDTH: return wxS( "annular_width" );
179 case kiapi::board::CRCT_ZONE_CONNECTION: return wxS( "zone_connection" );
180 case kiapi::board::CRCT_THERMAL_RELIEF_GAP: return wxS( "thermal_relief_gap" );
181 case kiapi::board::CRCT_THERMAL_SPOKE_WIDTH: return wxS( "thermal_spoke_width" );
182 case kiapi::board::CRCT_MIN_RESOLVED_SPOKES: return wxS( "min_resolved_spokes" );
183 case kiapi::board::CRCT_SOLDER_MASK_EXPANSION: return wxS( "solder_mask_expansion" );
184 case kiapi::board::CRCT_SOLDER_PASTE_ABS_MARGIN: return wxS( "solder_paste_abs_margin" );
185 case kiapi::board::CRCT_SOLDER_PASTE_REL_MARGIN: return wxS( "solder_paste_rel_margin" );
186 case kiapi::board::CRCT_DISALLOW: return wxS( "disallow" );
187 case kiapi::board::CRCT_VIA_DIAMETER: return wxS( "via_diameter" );
188 case kiapi::board::CRCT_LENGTH: return wxS( "length" );
189 case kiapi::board::CRCT_SKEW: return wxS( "skew" );
190 case kiapi::board::CRCT_DIFF_PAIR_GAP: return wxS( "diff_pair_gap" );
191 case kiapi::board::CRCT_MAX_UNCOUPLED: return wxS( "diff_pair_uncoupled" );
192 case kiapi::board::CRCT_DIFF_PAIR_INTRA_SKEW:
193 {
194 if( aAddWithinDiffPairs )
195 *aAddWithinDiffPairs = true;
196
197 return wxS( "skew" );
198 }
199 case kiapi::board::CRCT_VIA_COUNT: return wxS( "via_count" );
200 case kiapi::board::CRCT_PHYSICAL_CLEARANCE: return wxS( "physical_clearance" );
201 case kiapi::board::CRCT_PHYSICAL_HOLE_CLEARANCE: return wxS( "physical_hole_clearance" );
202 case kiapi::board::CRCT_ASSERTION: return wxS( "assertion" );
203 case kiapi::board::CRCT_CONNECTION_WIDTH: return wxS( "connection_width" );
204 case kiapi::board::CRCT_TRACK_ANGLE: return wxS( "track_angle" );
205 case kiapi::board::CRCT_VIA_DANGLING: return wxS( "via_dangling" );
206 case kiapi::board::CRCT_BRIDGED_MASK: return wxS( "bridged_mask" );
207 case kiapi::board::CRCT_SOLDER_MASK_SLIVER: return wxS( "solder_mask_sliver" );
208 case kiapi::board::CRCT_NET_CHAIN_LENGTH: return wxS( "net_chain_length" );
209 case kiapi::board::CRCT_NET_CHAIN_STUB_LENGTH: return wxS( "stub_length" );
210 case kiapi::board::CRCT_NET_CHAIN_RETURN_PATH: return wxS( "return_path" );
211 case kiapi::board::CRCT_MICROVIA_STACK_DEPTH: return wxS( "microvia_stack_depth" );
212 case kiapi::board::CRCT_HOLE_ASPECT_RATIO: return wxS( "hole_aspect_ratio" );
213
214 case kiapi::board::CRCT_UNKNOWN:
215 default:
216 return std::nullopt;
217 }
218 };
219
220 auto formatNumericValueForConstraint =
221 [&]( const kiapi::board::CustomRuleConstraint& aConstraint,
222 const int aValue ) -> wxString
223 {
224 if( aConstraint.type() == kiapi::board::CRCT_TRACK_ANGLE )
225 return wxString::Format( "%ddeg", aValue );
226
227 if( aConstraint.type() == kiapi::board::CRCT_HOLE_ASPECT_RATIO )
228 return wxString::FromUTF8( FormatDouble2Str( aValue / 1000.0 ) );
229
230 switch( aConstraint.type() )
231 {
232 case kiapi::board::CRCT_VIA_COUNT:
233 case kiapi::board::CRCT_MIN_RESOLVED_SPOKES:
234 case kiapi::board::CRCT_VIA_DANGLING:
235 case kiapi::board::CRCT_BRIDGED_MASK:
236 case kiapi::board::CRCT_MICROVIA_STACK_DEPTH:
237 return wxString::Format( "%d", aValue );
238
239 default:
240 break;
241 }
242
243 if( hasConstraintOption( aConstraint, kiapi::board::CRCO_TIME_DOMAIN ) )
244 {
245 double picoseconds = aValue / pcbIUScale.IU_PER_PS;
246 return wxString::FromUTF8( FormatDouble2Str( picoseconds ) ) + "ps";
247 }
248
249 wxString formatted = wxString::FromUTF8( EDA_UNIT_UTILS::FormatInternalUnits( pcbIUScale, aValue ) );
250 return formatted + "mm";
251 };
252
253 auto severityToken =
254 []( const kiapi::common::types::RuleSeverity aSeverity ) -> wxString
255 {
256 switch( aSeverity )
257 {
258 case kiapi::common::types::RuleSeverity::RS_WARNING: return wxS( "warning" );
259 case kiapi::common::types::RuleSeverity::RS_ERROR: return wxS( "error" );
260 case kiapi::common::types::RuleSeverity::RS_EXCLUSION: return wxS( "exclusion" );
261 case kiapi::common::types::RuleSeverity::RS_IGNORE: return wxS( "ignore" );
262
263 default:
264 return wxEmptyString;
265 }
266 };
267
268 if( aRule.name().empty() )
269 return fail( wxS( "Rules must have a name" ) );
270
271 wxString ruleText;
272 ruleText << "(rule \"" << escapeQuotedRuleText( wxString::FromUTF8( aRule.name() ) ) << "\"\n";
273
274 if( aRule.has_comments() && !aRule.comments().empty() )
275 {
276 wxArrayString commentLines = wxSplit( wxString::FromUTF8( aRule.comments() ), '\n', '\0' );
277
278 for( const wxString& line : commentLines )
279 ruleText << "\t# " << line << "\n";
280 }
281
282 if( !aRule.condition().empty() )
283 {
284 ruleText << "\t(condition \""
285 << escapeQuotedRuleText( wxString::FromUTF8( aRule.condition() ) )
286 << "\")\n";
287 }
288
289 if( aRule.layer_condition_case() == kiapi::board::CustomRule::LayerConditionCase::kLayerMode )
290 {
291 switch( aRule.layer_mode() )
292 {
293 case kiapi::board::CRLM_OUTER:
294 ruleText << "\t(layer outer)\n";
295 break;
296
297 case kiapi::board::CRLM_INNER:
298 ruleText << "\t(layer inner)\n";
299 break;
300
301 case kiapi::board::CRLM_UNKNOWN:
302 default:
303 break;
304 }
305 }
306 else if( aRule.layer_condition_case() == kiapi::board::CustomRule::LayerConditionCase::kSingleLayer )
307 {
308 PCB_LAYER_ID layer =
310
311 if( layer == UNDEFINED_LAYER || layer == UNSELECTED_LAYER )
312 return fail( wxS( "Invalid single_layer value in custom rule" ) );
313
314 ruleText << "\t(layer \"" << BOARD::GetStandardLayerName( layer ) << "\")\n";
315 }
316
317 for( const kiapi::board::CustomRuleConstraint& constraint : aRule.constraints() )
318 {
319 bool addWithinDiffPairs = false;
320 std::optional<wxString> token = getConstraintToken( constraint.type(), &addWithinDiffPairs );
321
322 if( !token )
323 return fail( wxS( "Unsupported custom rule constraint type" ) );
324
325 wxString text = wxS( "\t(constraint " ) + *token;
326
327 if( constraint.has_name() && !constraint.name().empty() )
328 {
329 text += wxS( " (name \"" )
330 + escapeQuotedRuleText( wxString::FromUTF8( constraint.name() ) )
331 + wxS( "\")" );
332 }
333
334 if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kDisallow )
335 {
336 for( int disallowTypeValue : constraint.disallow().types() )
337 {
338 kiapi::board::CustomRuleDisallowType disallowType =
339 static_cast<kiapi::board::CustomRuleDisallowType>( disallowTypeValue );
340
341 switch( disallowType )
342 {
343 case kiapi::board::CRDT_THROUGH_VIAS: text += wxS( " through_via" ); break;
344 case kiapi::board::CRDT_MICRO_VIAS: text += wxS( " micro_via" ); break;
345 case kiapi::board::CRDT_BLIND_VIAS: text += wxS( " blind_via" ); break;
346 case kiapi::board::CRDT_BURIED_VIAS: text += wxS( " buried_via" ); break;
347 case kiapi::board::CRDT_TRACKS: text += wxS( " track" ); break;
348 case kiapi::board::CRDT_PADS: text += wxS( " pad" ); break;
349 case kiapi::board::CRDT_ZONES: text += wxS( " zone" ); break;
350 case kiapi::board::CRDT_TEXTS: text += wxS( " text" ); break;
351 case kiapi::board::CRDT_GRAPHICS: text += wxS( " graphic" ); break;
352 case kiapi::board::CRDT_HOLES: text += wxS( " hole" ); break;
353 case kiapi::board::CRDT_FOOTPRINTS: text += wxS( " footprint" ); break;
354
355 case kiapi::board::CRDT_UNKNOWN:
356 default:
357 break;
358 }
359 }
360 }
361 else if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kZoneConnection )
362 {
363 switch( constraint.zone_connection() )
364 {
365 case kiapi::board::types::ZCS_FULL:
366 text += wxS( " solid" );
367 break;
368
369 case kiapi::board::types::ZCS_THERMAL:
370 text += wxS( " thermal_reliefs" );
371 break;
372
373 case kiapi::board::types::ZCS_NONE:
374 text += wxS( " none" );
375 break;
376
377 default:
378 return fail( wxS( "Unsupported zone connection style" ) );
379 }
380 }
381 else if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kAssertionExpression )
382 {
383 text += wxS( " \"" )
384 + escapeQuotedRuleText( wxString::FromUTF8( constraint.assertion_expression() ) )
385 + wxS( "\"" );
386 }
387 else if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kReturnPath )
388 {
389 const kiapi::board::CustomRuleReturnPathSettings& returnPath = constraint.return_path();
390
391 if( !returnPath.reference_layer().empty() )
392 {
393 text += wxS( " (layer \"" )
394 + escapeQuotedRuleText( wxString::FromUTF8( returnPath.reference_layer() ) )
395 + wxS( "\")" );
396 }
397
398 if( !returnPath.reference_net().empty() )
399 {
400 text += wxS( " (net \"" )
401 + escapeQuotedRuleText( wxString::FromUTF8( returnPath.reference_net() ) )
402 + wxS( "\")" );
403 }
404 }
405 else if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kNumeric )
406 {
407 const kiapi::common::types::MinOptMax& numeric = constraint.numeric();
408
409 if( numeric.has_min() )
410 text += wxS( " (min " )
411 + formatNumericValueForConstraint( constraint, numeric.min() )
412 + wxS( ")" );
413
414 if( numeric.has_opt() )
415 text += wxS( " (opt " )
416 + formatNumericValueForConstraint( constraint, numeric.opt() )
417 + wxS( ")" );
418
419 if( numeric.has_max() )
420 text += wxS( " (max " )
421 + formatNumericValueForConstraint( constraint, numeric.max() )
422 + wxS( ")" );
423 }
424
425 if( addWithinDiffPairs
426 || hasConstraintOption( constraint, kiapi::board::CRCO_SKEW_WITHIN_DIFF_PAIRS ) )
427 {
428 text += wxS( " (within_diff_pairs)" );
429 }
430
431 text += wxS( ")" );
432 ruleText << text << "\n";
433 }
434
435 wxString severity = severityToken( aRule.severity() );
436
437 if( !severity.IsEmpty() )
438 ruleText << "\t(severity " << severity << ")\n";
439
440 ruleText << ")\n";
441 return ruleText;
442}
443
444
445void DRC_CONSTRAINT::ToProto( kiapi::board::CustomRuleConstraint& aProto ) const
446{
448
449 if( !m_name.IsEmpty() )
450 aProto.set_name( m_name.ToUTF8() );
451
452 for( size_t ii = 0; ii < static_cast<size_t>( OPTIONS::NUM_OPTIONS ); ++ii )
453 {
454 OPTIONS option = static_cast<OPTIONS>( ii );
455
456 if( GetOption( option ) )
458 }
459
461 {
462 kiapi::board::CustomRuleDisallowSettings* disallow = aProto.mutable_disallow();
463
465 {
466 if( m_DisallowFlags & flag )
467 {
469 static_cast<DRC_DISALLOW_T>( flag ) ) );
470 }
471 }
472 }
474 {
475 aProto.set_zone_connection(
477 }
478 else if( m_Type == ASSERTION_CONSTRAINT )
479 {
480 if( m_Test )
481 aProto.set_assertion_expression( m_Test->GetExpression().ToUTF8() );
482 }
484 {
485 kiapi::board::CustomRuleReturnPathSettings* returnPath = aProto.mutable_return_path();
486 returnPath->set_reference_layer( m_ReferenceLayer.ToUTF8() );
487 returnPath->set_reference_net( m_ReferenceNet.ToUTF8() );
488 }
489 else
490 {
491 kiapi::common::types::MinOptMax* numeric = aProto.mutable_numeric();
492
493 if( m_Value.HasMin() )
494 numeric->set_min( m_Value.Min() );
495
496 if( m_Value.HasOpt() )
497 numeric->set_opt( m_Value.Opt() );
498
499 if( m_Value.HasMax() )
500 numeric->set_max( m_Value.Max() );
501 }
502}
KICOMMON_API types::KiCadObjectType ToProtoEnum(KICAD_T aValue)
KICOMMON_API KICAD_T FromProtoEnum(types::KiCadObjectType aValue)
Definition api_enums.cpp:55
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
static wxString GetStandardLayerName(PCB_LAYER_ID aLayerId)
Return an "English Standard" name of a PCB layer when given aLayerNumber.
Definition board.h:1122
DRC_RULE_CONDITION * m_Test
Definition drc_rule.h:207
void ToProto(kiapi::board::CustomRuleConstraint &aProto) const
Definition drc_rule.cpp:445
int m_DisallowFlags
Definition drc_rule.h:205
void SetParentRule(DRC_RULE *aParentRule)
Attribute the constraint, including each min, opt and max value it has, to aParentRule.
Definition drc_rule.h:135
ZONE_CONNECTION m_ZoneConnection
Definition drc_rule.h:206
MINOPTMAX< int > m_Value
Definition drc_rule.h:204
wxString m_ReferenceLayer
Definition drc_rule.h:213
DRC_CONSTRAINT_T m_Type
Definition drc_rule.h:203
bool IsUnary() const
Definition drc_rule.cpp:88
wxString m_name
Definition drc_rule.h:221
bool GetOption(OPTIONS option) const
Definition drc_rule.h:193
wxString m_ReferenceNet
Definition drc_rule.h:218
SEVERITY m_Severity
Definition drc_rule.h:86
bool m_Unary
Definition drc_rule.h:77
DRC_RULE_CONDITION * m_Condition
Definition drc_rule.h:84
LSET m_LayerCondition
Definition drc_rule.h:83
static wxString FormatRuleFromProto(const kiapi::board::CustomRule &aRule, wxString *aErrorText=nullptr)
Definition drc_rule.cpp:121
KIID m_ImplicitItemId
Definition drc_rule.h:78
std::vector< DRC_CONSTRAINT > m_Constraints
Definition drc_rule.h:85
BOARD_ITEM * m_ImplicitItem
Definition drc_rule.h:79
wxString m_Name
Definition drc_rule.h:80
void AddConstraint(DRC_CONSTRAINT &aConstraint)
Definition drc_rule.cpp:69
virtual ~DRC_RULE()
Definition drc_rule.cpp:63
std::optional< DRC_CONSTRAINT > FindConstraint(DRC_CONSTRAINT_T aType)
Definition drc_rule.cpp:76
DRC_IMPLICIT_SOURCE m_implicitSource
Definition drc_rule.h:89
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
DRC_IMPLICIT_SOURCE
@ DRC_DISALLOW_THROUGH_VIAS
@ DRC_DISALLOW_FOOTPRINTS
@ ANNULAR_WIDTH_CONSTRAINT
@ BRIDGED_MASK_CONSTRAINT
@ VIA_DIAMETER_CONSTRAINT
@ ZONE_CONNECTION_CONSTRAINT
@ NET_CHAIN_LENGTH_CONSTRAINT
@ VIA_DANGLING_CONSTRAINT
@ NET_CHAIN_STUB_LENGTH_CONSTRAINT
@ DISALLOW_CONSTRAINT
@ TRACK_SEGMENT_LENGTH_CONSTRAINT
@ HOLE_ASPECT_RATIO_CONSTRAINT
@ TEXT_THICKNESS_CONSTRAINT
@ LENGTH_CONSTRAINT
@ VIA_COUNT_CONSTRAINT
@ NET_CHAIN_RETURN_PATH_CONSTRAINT
@ ASSERTION_CONSTRAINT
@ SKEW_CONSTRAINT
@ MICROVIA_STACK_DEPTH_CONSTRAINT
@ SOLDER_PASTE_ABS_MARGIN_CONSTRAINT
@ SOLDER_MASK_EXPANSION_CONSTRAINT
@ HOLE_SIZE_CONSTRAINT
@ TEXT_HEIGHT_CONSTRAINT
@ SOLDER_PASTE_REL_MARGIN_CONSTRAINT
bool IsComponentClassSelector(const wxString &aToken)
${Class:X} inside a DRC rule is a component-class selector consumed by testFootprintSelector(),...
Definition drc_rule.cpp:32
@ NONE
Definition eda_fill.h:42
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ UNSELECTED_LAYER
Definition layer_ids.h:58
@ UNDEFINED_LAYER
Definition layer_ids.h:57
KICOMMON_API std::string FormatInternalUnits(const EDA_IU_SCALE &aIuScale, int aValue, EDA_DATA_TYPE aDataType=EDA_DATA_TYPE::DISTANCE)
Converts aValue from internal units to a string appropriate for writing to file.
@ RPT_SEVERITY_UNDEFINED
std::string FormatDouble2Str(double aValue)
Print a float number without using scientific notation and no trailing 0 This function is intended in...