124 *aErrorText = wxEmptyString;
127 [&](
const wxString& aMessage ) -> wxString
130 *aErrorText = aMessage;
132 return wxEmptyString;
135 auto escapeQuotedRuleText =
136 [](
const wxString& aText ) -> wxString
138 wxString escaped = aText;
139 escaped.Replace(
"\\",
"\\\\" );
140 escaped.Replace(
"\"",
"\\\"" );
144 auto hasConstraintOption =
145 [](
const kiapi::board::CustomRuleConstraint& aConstraint,
146 const kiapi::board::CustomRuleConstraintOption aOption ) ->
bool
148 for(
int optionValue : aConstraint.options() )
150 if(
static_cast<kiapi::board::CustomRuleConstraintOption
>( optionValue ) == aOption )
157 auto getConstraintToken =
158 [](
const kiapi::board::CustomRuleConstraintType aType,
159 bool* aAddWithinDiffPairs ) -> std::optional<wxString>
161 if( aAddWithinDiffPairs )
162 *aAddWithinDiffPairs =
false;
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:
194 if( aAddWithinDiffPairs )
195 *aAddWithinDiffPairs =
true;
197 return wxS(
"skew" );
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" );
214 case kiapi::board::CRCT_UNKNOWN:
220 auto formatNumericValueForConstraint =
221 [&](
const kiapi::board::CustomRuleConstraint& aConstraint,
222 const int aValue ) -> wxString
224 if( aConstraint.type() == kiapi::board::CRCT_TRACK_ANGLE )
225 return wxString::Format(
"%ddeg", aValue );
227 if( aConstraint.type() == kiapi::board::CRCT_HOLE_ASPECT_RATIO )
230 switch( aConstraint.type() )
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 );
243 if( hasConstraintOption( aConstraint, kiapi::board::CRCO_TIME_DOMAIN ) )
245 double picoseconds = aValue /
pcbIUScale.IU_PER_PS;
250 return formatted +
"mm";
254 [](
const kiapi::common::types::RuleSeverity aSeverity ) -> wxString
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" );
264 return wxEmptyString;
268 if( aRule.name().empty() )
269 return fail( wxS(
"Rules must have a name" ) );
272 ruleText <<
"(rule \"" << escapeQuotedRuleText( wxString::FromUTF8( aRule.name() ) ) <<
"\"\n";
274 if( aRule.has_comments() && !aRule.comments().empty() )
276 wxArrayString commentLines = wxSplit( wxString::FromUTF8( aRule.comments() ),
'\n',
'\0' );
278 for(
const wxString& line : commentLines )
279 ruleText <<
"\t# " << line <<
"\n";
282 if( !aRule.condition().empty() )
284 ruleText <<
"\t(condition \""
285 << escapeQuotedRuleText( wxString::FromUTF8( aRule.condition() ) )
289 if( aRule.layer_condition_case() == kiapi::board::CustomRule::LayerConditionCase::kLayerMode )
291 switch( aRule.layer_mode() )
293 case kiapi::board::CRLM_OUTER:
294 ruleText <<
"\t(layer outer)\n";
297 case kiapi::board::CRLM_INNER:
298 ruleText <<
"\t(layer inner)\n";
301 case kiapi::board::CRLM_UNKNOWN:
306 else if( aRule.layer_condition_case() == kiapi::board::CustomRule::LayerConditionCase::kSingleLayer )
312 return fail( wxS(
"Invalid single_layer value in custom rule" ) );
317 for(
const kiapi::board::CustomRuleConstraint& constraint : aRule.constraints() )
319 bool addWithinDiffPairs =
false;
320 std::optional<wxString> token = getConstraintToken( constraint.type(), &addWithinDiffPairs );
323 return fail( wxS(
"Unsupported custom rule constraint type" ) );
325 wxString
text = wxS(
"\t(constraint " ) + *token;
327 if( constraint.has_name() && !constraint.name().empty() )
329 text += wxS(
" (name \"" )
330 + escapeQuotedRuleText( wxString::FromUTF8( constraint.name() ) )
334 if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kDisallow )
336 for(
int disallowTypeValue : constraint.disallow().types() )
338 kiapi::board::CustomRuleDisallowType disallowType =
339 static_cast<kiapi::board::CustomRuleDisallowType
>( disallowTypeValue );
341 switch( disallowType )
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;
355 case kiapi::board::CRDT_UNKNOWN:
361 else if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kZoneConnection )
363 switch( constraint.zone_connection() )
365 case kiapi::board::types::ZCS_FULL:
366 text += wxS(
" solid" );
369 case kiapi::board::types::ZCS_THERMAL:
370 text += wxS(
" thermal_reliefs" );
373 case kiapi::board::types::ZCS_NONE:
374 text += wxS(
" none" );
378 return fail( wxS(
"Unsupported zone connection style" ) );
381 else if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kAssertionExpression )
384 + escapeQuotedRuleText( wxString::FromUTF8( constraint.assertion_expression() ) )
387 else if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kReturnPath )
389 const kiapi::board::CustomRuleReturnPathSettings& returnPath = constraint.return_path();
391 if( !returnPath.reference_layer().empty() )
393 text += wxS(
" (layer \"" )
394 + escapeQuotedRuleText( wxString::FromUTF8( returnPath.reference_layer() ) )
398 if( !returnPath.reference_net().empty() )
400 text += wxS(
" (net \"" )
401 + escapeQuotedRuleText( wxString::FromUTF8( returnPath.reference_net() ) )
405 else if( constraint.value_case() == kiapi::board::CustomRuleConstraint::ValueCase::kNumeric )
407 const kiapi::common::types::MinOptMax& numeric = constraint.numeric();
409 if( numeric.has_min() )
410 text += wxS(
" (min " )
411 + formatNumericValueForConstraint( constraint, numeric.min() )
414 if( numeric.has_opt() )
415 text += wxS(
" (opt " )
416 + formatNumericValueForConstraint( constraint, numeric.opt() )
419 if( numeric.has_max() )
420 text += wxS(
" (max " )
421 + formatNumericValueForConstraint( constraint, numeric.max() )
425 if( addWithinDiffPairs
426 || hasConstraintOption( constraint, kiapi::board::CRCO_SKEW_WITHIN_DIFF_PAIRS ) )
428 text += wxS(
" (within_diff_pairs)" );
432 ruleText <<
text <<
"\n";
435 wxString severity = severityToken( aRule.severity() );
437 if( !severity.IsEmpty() )
438 ruleText <<
"\t(severity " << severity <<
")\n";