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 <base_units.h>
26#include <drc/drc_rule.h>
28
29
30bool IsComponentClassSelector( const wxString& aToken )
31{
32 return aToken.Upper().StartsWith( wxT( "CLASS:" ) );
33}
34
35
37 m_Unary( false ),
39 m_ImplicitItem( nullptr ),
40 m_LayerCondition( LSET::AllLayersMask() ),
41 m_Condition( nullptr ),
44{
45}
46
47
48DRC_RULE::DRC_RULE( const wxString& aName ) :
49 m_Unary( false ),
51 m_ImplicitItem( nullptr ),
52 m_Name( aName ),
53 m_LayerCondition( LSET::AllLayersMask() ),
54 m_Condition( nullptr ),
57{
58}
59
60
62{
63 delete m_Condition;
64}
65
66
68{
69 aConstraint.SetParentRule( this );
70 m_Constraints.push_back( aConstraint );
71}
72
73
74std::optional<DRC_CONSTRAINT> DRC_RULE::FindConstraint( DRC_CONSTRAINT_T aType )
75{
77 {
78 if( c.m_Type == aType )
79 return c;
80 }
81
82 return std::optional<DRC_CONSTRAINT>();
83}
84
85
86wxString DRC_RULE::FormatRuleFromProto( const kiapi::board::CustomRule& aRule, wxString* aErrorText )
87{
88 if( aErrorText )
89 *aErrorText = wxEmptyString;
90
91 auto fail =
92 [&]( const wxString& aMessage ) -> wxString
93 {
94 if( aErrorText )
95 *aErrorText = aMessage;
96
97 return wxEmptyString;
98 };
99
100 auto escapeQuotedRuleText =
101 []( const wxString& aText ) -> wxString
102 {
103 wxString escaped = aText;
104 escaped.Replace( "\\", "\\\\" );
105 escaped.Replace( "\"", "\\\"" );
106 return escaped;
107 };
108
109 auto hasConstraintOption =
110 []( const kiapi::board::CustomRuleConstraint& aConstraint,
111 const kiapi::board::CustomRuleConstraintOption aOption ) -> bool
112 {
113 for( int optionValue : aConstraint.options() )
114 {
115 if( static_cast<kiapi::board::CustomRuleConstraintOption>( optionValue ) == aOption )
116 return true;
117 }
118
119 return false;
120 };
121
122 auto getConstraintToken =
123 []( const kiapi::board::CustomRuleConstraintType aType,
124 bool* aAddWithinDiffPairs ) -> std::optional<wxString>
125 {
126 if( aAddWithinDiffPairs )
127 *aAddWithinDiffPairs = false;
128
129 switch( aType )
130 {
131 case kiapi::board::CRCT_CLEARANCE: return wxS( "clearance" );
132 case kiapi::board::CRCT_CREEPAGE: return wxS( "creepage" );
133 case kiapi::board::CRCT_HOLE_CLEARANCE: return wxS( "hole_clearance" );
134 case kiapi::board::CRCT_HOLE_TO_HOLE: return wxS( "hole_to_hole" );
135 case kiapi::board::CRCT_EDGE_CLEARANCE: return wxS( "edge_clearance" );
136 case kiapi::board::CRCT_HOLE_SIZE: return wxS( "hole_size" );
137 case kiapi::board::CRCT_COURTYARD_CLEARANCE: return wxS( "courtyard_clearance" );
138 case kiapi::board::CRCT_SILK_CLEARANCE: return wxS( "silk_clearance" );
139 case kiapi::board::CRCT_TEXT_HEIGHT: return wxS( "text_height" );
140 case kiapi::board::CRCT_TEXT_THICKNESS: return wxS( "text_thickness" );
141 case kiapi::board::CRCT_TRACK_WIDTH: return wxS( "track_width" );
142 case kiapi::board::CRCT_TRACK_SEGMENT_LENGTH: return wxS( "track_segment_length" );
143 case kiapi::board::CRCT_ANNULAR_WIDTH: return wxS( "annular_width" );
144 case kiapi::board::CRCT_ZONE_CONNECTION: return wxS( "zone_connection" );
145 case kiapi::board::CRCT_THERMAL_RELIEF_GAP: return wxS( "thermal_relief_gap" );
146 case kiapi::board::CRCT_THERMAL_SPOKE_WIDTH: return wxS( "thermal_spoke_width" );
147 case kiapi::board::CRCT_MIN_RESOLVED_SPOKES: return wxS( "min_resolved_spokes" );
148 case kiapi::board::CRCT_SOLDER_MASK_EXPANSION: return wxS( "solder_mask_expansion" );
149 case kiapi::board::CRCT_SOLDER_PASTE_ABS_MARGIN: return wxS( "solder_paste_abs_margin" );
150 case kiapi::board::CRCT_SOLDER_PASTE_REL_MARGIN: return wxS( "solder_paste_rel_margin" );
151 case kiapi::board::CRCT_DISALLOW: return wxS( "disallow" );
152 case kiapi::board::CRCT_VIA_DIAMETER: return wxS( "via_diameter" );
153 case kiapi::board::CRCT_LENGTH: return wxS( "length" );
154 case kiapi::board::CRCT_SKEW: return wxS( "skew" );
155 case kiapi::board::CRCT_DIFF_PAIR_GAP: return wxS( "diff_pair_gap" );
156 case kiapi::board::CRCT_MAX_UNCOUPLED: return wxS( "diff_pair_uncoupled" );
157 case kiapi::board::CRCT_DIFF_PAIR_INTRA_SKEW:
158 {
159 if( aAddWithinDiffPairs )
160 *aAddWithinDiffPairs = true;
161
162 return wxS( "skew" );
163 }
164 case kiapi::board::CRCT_VIA_COUNT: return wxS( "via_count" );
165 case kiapi::board::CRCT_PHYSICAL_CLEARANCE: return wxS( "physical_clearance" );
166 case kiapi::board::CRCT_PHYSICAL_HOLE_CLEARANCE: return wxS( "physical_hole_clearance" );
167 case kiapi::board::CRCT_ASSERTION: return wxS( "assertion" );
168 case kiapi::board::CRCT_CONNECTION_WIDTH: return wxS( "connection_width" );
169 case kiapi::board::CRCT_TRACK_ANGLE: return wxS( "track_angle" );
170 case kiapi::board::CRCT_VIA_DANGLING: return wxS( "via_dangling" );
171 case kiapi::board::CRCT_BRIDGED_MASK: return wxS( "bridged_mask" );
172 case kiapi::board::CRCT_SOLDER_MASK_SLIVER: return wxS( "solder_mask_sliver" );
173
174 case kiapi::board::CRCT_UNKNOWN:
175 default:
176 return std::nullopt;
177 }
178 };
179
180 auto formatNumericValueForConstraint =
181 [&]( const kiapi::board::CustomRuleConstraint& aConstraint,
182 const int aValue ) -> wxString
183 {
184 if( aConstraint.type() == kiapi::board::CRCT_TRACK_ANGLE )
185 return wxString::Format( "%ddeg", aValue );
186
187 switch( aConstraint.type() )
188 {
189 case kiapi::board::CRCT_VIA_COUNT:
190 case kiapi::board::CRCT_MIN_RESOLVED_SPOKES:
191 case kiapi::board::CRCT_VIA_DANGLING:
192 case kiapi::board::CRCT_BRIDGED_MASK:
193 return wxString::Format( "%d", aValue );
194
195 default:
196 break;
197 }
198
199 if( hasConstraintOption( aConstraint, kiapi::board::CRCO_TIME_DOMAIN ) )
200 return wxString::Format( "%dps", aValue );
201
202 wxString formatted = wxString::FromUTF8( EDA_UNIT_UTILS::FormatInternalUnits( pcbIUScale, aValue ) );
203 return formatted + "mm";
204 };
205
206 auto severityToken =
207 []( const kiapi::common::types::RuleSeverity aSeverity ) -> wxString
208 {
209 switch( aSeverity )
210 {
211 case kiapi::common::types::RuleSeverity::RS_WARNING: return wxS( "warning" );
212 case kiapi::common::types::RuleSeverity::RS_ERROR: return wxS( "error" );
213 case kiapi::common::types::RuleSeverity::RS_EXCLUSION: return wxS( "exclusion" );
214 case kiapi::common::types::RuleSeverity::RS_IGNORE: return wxS( "ignore" );
215
216 default:
217 return wxEmptyString;
218 }
219 };
220
221 if( aRule.name().empty() )
222 return fail( wxS( "Rules must have a name" ) );
223
224 wxString ruleText;
225 ruleText << "(rule \"" << escapeQuotedRuleText( wxString::FromUTF8( aRule.name() ) ) << "\"\n";
226
227 if( aRule.has_comments() && !aRule.comments().empty() )
228 {
229 wxArrayString commentLines = wxSplit( wxString::FromUTF8( aRule.comments() ), '\n', '\0' );
230
231 for( const wxString& line : commentLines )
232 ruleText << "\t# " << line << "\n";
233 }
234
235 if( !aRule.condition().empty() )
236 {
237 ruleText << "\t(condition \""
238 << escapeQuotedRuleText( wxString::FromUTF8( aRule.condition() ) )
239 << "\")\n";
240 }
241
242 if( aRule.layer_condition_case() == kiapi::board::CustomRule::LayerConditionCase::kLayerMode )
243 {
244 switch( aRule.layer_mode() )
245 {
246 case kiapi::board::CRLM_OUTER:
247 ruleText << "\t(layer outer)\n";
248 break;
249
250 case kiapi::board::CRLM_INNER:
251 ruleText << "\t(layer inner)\n";
252 break;
253
254 case kiapi::board::CRLM_UNKNOWN:
255 default:
256 break;
257 }
258 }
259 else if( aRule.layer_condition_case() == kiapi::board::CustomRule::LayerConditionCase::kSingleLayer )
260 {
261 PCB_LAYER_ID layer =
263
264 if( layer == UNDEFINED_LAYER || layer == UNSELECTED_LAYER )
265 return fail( wxS( "Invalid single_layer value in custom rule" ) );
266
267 ruleText << "\t(layer \"" << BOARD::GetStandardLayerName( layer ) << "\")\n";
268 }
269
270 for( const kiapi::board::CustomRuleConstraint& constraint : aRule.constraints() )
271 {
272 bool addWithinDiffPairs = false;
273 std::optional<wxString> token = getConstraintToken( constraint.type(), &addWithinDiffPairs );
274
275 if( !token )
276 return fail( wxS( "Unsupported custom rule constraint type" ) );
277
278 wxString text = wxS( "\t(constraint " ) + *token;
279
280 if( constraint.has_name() && !constraint.name().empty() )
281 {
282 text += wxS( " (name \"" )
283 + escapeQuotedRuleText( wxString::FromUTF8( constraint.name() ) )
284 + wxS( "\")" );
285 }
286
287 if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kDisallow )
288 {
289 for( int disallowTypeValue : constraint.disallow().types() )
290 {
291 kiapi::board::CustomRuleDisallowType disallowType =
292 static_cast<kiapi::board::CustomRuleDisallowType>( disallowTypeValue );
293
294 switch( disallowType )
295 {
296 case kiapi::board::CRDT_THROUGH_VIAS: text += wxS( " through_via" ); break;
297 case kiapi::board::CRDT_MICRO_VIAS: text += wxS( " micro_via" ); break;
298 case kiapi::board::CRDT_BLIND_VIAS: text += wxS( " blind_via" ); break;
299 case kiapi::board::CRDT_BURIED_VIAS: text += wxS( " buried_via" ); break;
300 case kiapi::board::CRDT_TRACKS: text += wxS( " track" ); break;
301 case kiapi::board::CRDT_PADS: text += wxS( " pad" ); break;
302 case kiapi::board::CRDT_ZONES: text += wxS( " zone" ); break;
303 case kiapi::board::CRDT_TEXTS: text += wxS( " text" ); break;
304 case kiapi::board::CRDT_GRAPHICS: text += wxS( " graphic" ); break;
305 case kiapi::board::CRDT_HOLES: text += wxS( " hole" ); break;
306 case kiapi::board::CRDT_FOOTPRINTS: text += wxS( " footprint" ); break;
307
308 case kiapi::board::CRDT_UNKNOWN:
309 default:
310 break;
311 }
312 }
313 }
314 else if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kZoneConnection )
315 {
316 switch( constraint.zone_connection() )
317 {
318 case kiapi::board::types::ZCS_FULL:
319 text += wxS( " solid" );
320 break;
321
322 case kiapi::board::types::ZCS_THERMAL:
323 text += wxS( " thermal_reliefs" );
324 break;
325
326 case kiapi::board::types::ZCS_NONE:
327 text += wxS( " none" );
328 break;
329
330 default:
331 return fail( wxS( "Unsupported zone connection style" ) );
332 }
333 }
334 else if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kAssertionExpression )
335 {
336 text += wxS( " \"" )
337 + escapeQuotedRuleText( wxString::FromUTF8( constraint.assertion_expression() ) )
338 + wxS( "\"" );
339 }
340 else if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kNumeric )
341 {
342 const kiapi::common::types::MinOptMax& numeric = constraint.numeric();
343
344 if( numeric.has_min() )
345 text += wxS( " (min " )
346 + formatNumericValueForConstraint( constraint, numeric.min() )
347 + wxS( ")" );
348
349 if( numeric.has_opt() )
350 text += wxS( " (opt " )
351 + formatNumericValueForConstraint( constraint, numeric.opt() )
352 + wxS( ")" );
353
354 if( numeric.has_max() )
355 text += wxS( " (max " )
356 + formatNumericValueForConstraint( constraint, numeric.max() )
357 + wxS( ")" );
358 }
359
360 if( addWithinDiffPairs
361 || hasConstraintOption( constraint, kiapi::board::CRCO_SKEW_WITHIN_DIFF_PAIRS ) )
362 {
363 text += wxS( " (within_diff_pairs)" );
364 }
365
366 text += wxS( ")" );
367 ruleText << text << "\n";
368 }
369
370 wxString severity = severityToken( aRule.severity() );
371
372 if( !severity.IsEmpty() )
373 ruleText << "\t(severity " << severity << ")\n";
374
375 ruleText << ")\n";
376 return ruleText;
377}
378
379
380void DRC_CONSTRAINT::ToProto( kiapi::board::CustomRuleConstraint& aProto ) const
381{
383
384 if( !m_name.IsEmpty() )
385 aProto.set_name( m_name.ToUTF8() );
386
387 for( size_t ii = 0; ii < static_cast<size_t>( OPTIONS::NUM_OPTIONS ); ++ii )
388 {
389 OPTIONS option = static_cast<OPTIONS>( ii );
390
391 if( GetOption( option ) )
393 }
394
396 {
397 kiapi::board::CustomRuleDisallowSettings* disallow = aProto.mutable_disallow();
398
400 {
401 if( m_DisallowFlags & flag )
402 {
404 static_cast<DRC_DISALLOW_T>( flag ) ) );
405 }
406 }
407 }
409 {
410 aProto.set_zone_connection(
412 }
413 else if( m_Type == ASSERTION_CONSTRAINT )
414 {
415 if( m_Test )
416 aProto.set_assertion_expression( m_Test->GetExpression().ToUTF8() );
417 }
418 else
419 {
420 kiapi::common::types::MinOptMax* numeric = aProto.mutable_numeric();
421
422 if( m_Value.HasMin() )
423 numeric->set_min( m_Value.Min() );
424
425 if( m_Value.HasOpt() )
426 numeric->set_opt( m_Value.Opt() );
427
428 if( m_Value.HasMax() )
429 numeric->set_max( m_Value.Max() );
430 }
431}
types::KiCadObjectType ToProtoEnum(KICAD_T aValue)
KICAD_T FromProtoEnum(types::KiCadObjectType aValue)
Definition api_enums.cpp:47
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
static wxString GetStandardLayerName(PCB_LAYER_ID aLayerId)
Return an "English Standard" name of a PCB layer when given aLayerNumber.
Definition board.h:1003
DRC_RULE_CONDITION * m_Test
Definition drc_rule.h:243
void ToProto(kiapi::board::CustomRuleConstraint &aProto) const
Definition drc_rule.cpp:380
int m_DisallowFlags
Definition drc_rule.h:241
void SetParentRule(DRC_RULE *aParentRule)
Definition drc_rule.h:199
ZONE_CONNECTION m_ZoneConnection
Definition drc_rule.h:242
MINOPTMAX< int > m_Value
Definition drc_rule.h:240
DRC_CONSTRAINT_T m_Type
Definition drc_rule.h:239
wxString m_name
Definition drc_rule.h:257
bool GetOption(OPTIONS option) const
Definition drc_rule.h:229
SEVERITY m_Severity
Definition drc_rule.h:158
bool m_Unary
Definition drc_rule.h:150
DRC_RULE_CONDITION * m_Condition
Definition drc_rule.h:156
LSET m_LayerCondition
Definition drc_rule.h:155
static wxString FormatRuleFromProto(const kiapi::board::CustomRule &aRule, wxString *aErrorText=nullptr)
Definition drc_rule.cpp:86
KIID m_ImplicitItemId
Definition drc_rule.h:151
std::vector< DRC_CONSTRAINT > m_Constraints
Definition drc_rule.h:157
BOARD_ITEM * m_ImplicitItem
Definition drc_rule.h:152
wxString m_Name
Definition drc_rule.h:153
void AddConstraint(DRC_CONSTRAINT &aConstraint)
Definition drc_rule.cpp:67
virtual ~DRC_RULE()
Definition drc_rule.cpp:61
std::optional< DRC_CONSTRAINT > FindConstraint(DRC_CONSTRAINT_T aType)
Definition drc_rule.cpp:74
DRC_IMPLICIT_SOURCE m_implicitSource
Definition drc_rule.h:161
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
bool IsComponentClassSelector(const wxString &aToken)
${Class:X} inside a DRC rule is a component-class selector consumed by testFootprintSelector(),...
Definition drc_rule.cpp:30
DRC_IMPLICIT_SOURCE
Definition drc_rule.h:110
DRC_DISALLOW_T
Definition drc_rule.h:94
@ DRC_DISALLOW_THROUGH_VIAS
Definition drc_rule.h:95
@ DRC_DISALLOW_FOOTPRINTS
Definition drc_rule.h:105
DRC_CONSTRAINT_T
Definition drc_rule.h:49
@ ZONE_CONNECTION_CONSTRAINT
Definition drc_rule.h:64
@ DISALLOW_CONSTRAINT
Definition drc_rule.h:71
@ ASSERTION_CONSTRAINT
Definition drc_rule.h:84
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