24#include <nlohmann/json.hpp>
42struct LEGACY_DRC_ERROR
44 const char* m_protoName;
45 const char* m_settingsKey;
46 bool m_requiresTen =
false;
51constexpr LEGACY_DRC_ERROR legacyDrcErrors[] = {
52 {
"DRCET_UNKNOWN",
nullptr },
53 {
"DRCET_UNCONNECTED_ITEMS",
"unconnected_items" },
54 {
"DRCET_SHORTING_ITEMS",
"shorting_items" },
55 {
"DRCET_ALLOWED_ITEMS",
"items_not_allowed" },
56 {
"DRCET_TEXT_ON_EDGECUTS",
"text_on_edge_cuts" },
57 {
"DRCET_CLEARANCE",
"clearance" },
58 {
"DRCET_CREEPAGE",
"creepage" },
59 {
"DRCET_TRACKS_CROSSING",
"tracks_crossing" },
60 {
"DRCET_EDGE_CLEARANCE",
"copper_edge_clearance" },
61 {
"DRCET_ZONES_INTERSECT",
"zones_intersect" },
62 {
"DRCET_ISOLATED_COPPER",
"isolated_copper" },
63 {
"DRCET_STARVED_THERMAL",
"starved_thermal" },
64 {
"DRCET_DANGLING_VIA",
"via_dangling" },
65 {
"DRCET_DANGLING_TRACK",
"track_dangling" },
66 {
"DRCET_DRILLED_HOLES_TOO_CLOSE",
"hole_to_hole" },
67 {
"DRCET_DRILLED_HOLES_COLOCATED",
"holes_co_located" },
68 {
"DRCET_HOLE_CLEARANCE",
"hole_clearance" },
69 {
"DRCET_CONNECTION_WIDTH",
"connection_width" },
70 {
"DRCET_TRACK_WIDTH",
"track_width" },
71 {
"DRCET_TRACK_ANGLE",
"track_angle" },
72 {
"DRCET_TRACK_SEGMENT_LENGTH",
"track_segment_length" },
73 {
"DRCET_ANNULAR_WIDTH",
"annular_width" },
74 {
"DRCET_DRILL_OUT_OF_RANGE",
"drill_out_of_range" },
75 {
"DRCET_VIA_DIAMETER",
"via_diameter" },
76 {
"DRCET_PADSTACK",
"padstack" },
77 {
"DRCET_PADSTACK_INVALID",
"padstack_invalid" },
78 {
"DRCET_MICROVIA_DRILL_OUT_OF_RANGE",
"microvia_drill_out_of_range" },
79 {
"DRCET_OVERLAPPING_FOOTPRINTS",
"courtyards_overlap" },
80 {
"DRCET_MISSING_COURTYARD",
"missing_courtyard" },
81 {
"DRCET_MALFORMED_COURTYARD",
"malformed_courtyard" },
82 {
"DRCET_PTH_IN_COURTYARD",
"pth_inside_courtyard" },
83 {
"DRCET_NPTH_IN_COURTYARD",
"npth_inside_courtyard" },
84 {
"DRCET_DISABLED_LAYER_ITEM",
"item_on_disabled_layer" },
85 {
"DRCET_INVALID_OUTLINE",
"invalid_outline" },
86 {
"DRCET_MISSING_FOOTPRINT",
"missing_footprint" },
87 {
"DRCET_DUPLICATE_FOOTPRINT",
"duplicate_footprints" },
88 {
"DRCET_NET_CONFLICT",
"net_conflict" },
89 {
"DRCET_EXTRA_FOOTPRINT",
"extra_footprint" },
90 {
"DRCET_SCHEMATIC_PARITY",
"footprint_symbol_mismatch" },
91 {
"DRCET_SCHEMATIC_FIELDS_PARITY",
"footprint_symbol_field_mismatch",
true },
92 {
"DRCET_FOOTPRINT_FILTERS",
"footprint_filters_mismatch" },
93 {
"DRCET_LIB_FOOTPRINT_ISSUES",
"lib_footprint_issues" },
94 {
"DRCET_LIB_FOOTPRINT_MISMATCH",
"lib_footprint_mismatch" },
95 {
"DRCET_UNRESOLVED_VARIABLE",
"unresolved_variable" },
96 {
"DRCET_ASSERTION_FAILURE",
"assertion_failure" },
97 {
"DRCET_GENERIC_WARNING",
"generic_warning" },
98 {
"DRCET_GENERIC_ERROR",
"generic_error" },
99 {
"DRCET_COPPER_SLIVER",
"copper_sliver" },
100 {
"DRCET_SILK_CLEARANCE",
"silk_overlap" },
101 {
"DRCET_SILK_MASK_CLEARANCE",
"silk_over_copper" },
102 {
"DRCET_SILK_EDGE_CLEARANCE",
"silk_edge_clearance" },
103 {
"DRCET_SOLDERMASK_BRIDGE",
"solder_mask_bridge" },
104 {
"DRCET_TEXT_HEIGHT",
"text_height" },
105 {
"DRCET_TEXT_THICKNESS",
"text_thickness" },
106 {
"DRCET_LENGTH_OUT_OF_RANGE",
"length_out_of_range" },
107 {
"DRCET_SKEW_OUT_OF_RANGE",
"skew_out_of_range" },
108 {
"DRCET_VIA_COUNT_OUT_OF_RANGE",
"too_many_vias" },
109 {
"DRCET_DIFF_PAIR_GAP_OUT_OF_RANGE",
"diff_pair_gap_out_of_range" },
110 {
"DRCET_DIFF_PAIR_UNCOUPLED_LENGTH_TOO_LONG",
"diff_pair_uncoupled_length_too_long" },
111 {
"DRCET_FOOTPRINT",
"footprint" },
112 {
"DRCET_FOOTPRINT_TYPE_MISMATCH",
"footprint_type_mismatch" },
113 {
"DRCET_PAD_TH_WITH_NO_HOLE",
"through_hole_pad_without_hole" },
114 {
"DRCET_MIRRORED_TEXT_ON_FRONT_LAYER",
"mirrored_text_on_front_layer" },
115 {
"DRCET_NONMIRRORED_TEXT_ON_BACK_LAYER",
"nonmirrored_text_on_back_layer" },
116 {
"DRCET_MISSING_TUNING_PROFILE",
"missing_tuning_profile",
true },
117 {
"DRCET_TUNING_PROFILE_IMPLICIT_RULES",
"tuning_profile_track_geometries",
true },
118 {
"DRCET_TRACK_ON_POST_MACHINED_LAYER",
"track_on_post_machined_layer",
true },
119 {
"DRCET_TRACK_NOT_CENTERED_ON_VIA",
"track_not_centered_on_via",
true },
123constexpr LEGACY_DRC_ERROR legacyErcErrors[] = {
124 {
"ERCET_UNKNOWN",
nullptr },
125 {
"ERCET_DUPLICATE_SHEET_NAME",
"duplicate_sheet_names" },
126 {
"ERCET_ENDPOINT_OFF_GRID",
"endpoint_off_grid" },
127 {
"ERCET_PIN_NOT_CONNECTED",
"pin_not_connected" },
128 {
"ERCET_PIN_NOT_DRIVEN",
"pin_not_driven" },
129 {
"ERCET_POWERPIN_NOT_DRIVEN",
"power_pin_not_driven" },
130 {
"ERCET_HIERARCHICAL_LABEL",
"hier_label_mismatch" },
131 {
"ERCET_NOCONNECT_CONNECTED",
"no_connect_connected" },
132 {
"ERCET_NOCONNECT_NOT_CONNECTED",
"no_connect_dangling" },
133 {
"ERCET_LABEL_NOT_CONNECTED",
"label_dangling" },
134 {
"ERCET_SIMILAR_LABELS",
"similar_labels" },
135 {
"ERCET_SIMILAR_POWER",
"similar_power" },
136 {
"ERCET_SIMILAR_LABEL_AND_POWER",
"similar_label_and_power" },
137 {
"ERCET_SINGLE_GLOBAL_LABEL",
"single_global_label" },
138 {
"ERCET_SAME_LOCAL_GLOBAL_LABEL",
"same_local_global_label" },
139 {
"ERCET_SAME_LOCAL_GLOBAL_POWER",
nullptr },
140 {
"ERCET_DIFFERENT_UNIT_FP",
"different_unit_footprint" },
141 {
"ERCET_MISSING_POWER_INPUT_PIN",
"missing_power_pin" },
142 {
"ERCET_MISSING_INPUT_PIN",
"missing_input_pin" },
143 {
"ERCET_MISSING_BIDI_PIN",
"missing_bidi_pin" },
144 {
"ERCET_MISSING_UNIT",
"missing_unit" },
145 {
"ERCET_DIFFERENT_UNIT_NET",
"different_unit_net" },
146 {
"ERCET_BUS_ALIAS_CONFLICT",
"bus_definition_conflict" },
147 {
"ERCET_DRIVER_CONFLICT",
"multiple_net_names" },
148 {
"ERCET_BUS_ENTRY_CONFLICT",
"net_not_bus_member" },
149 {
"ERCET_BUS_TO_BUS_CONFLICT",
"bus_to_bus_conflict" },
150 {
"ERCET_BUS_TO_NET_CONFLICT",
"bus_to_net_conflict" },
151 {
"ERCET_GROUND_PIN_NOT_GROUND",
"ground_pin_not_ground",
true },
152 {
"ERCET_LABEL_SINGLE_PIN",
"isolated_pin_label",
true },
153 {
"ERCET_UNRESOLVED_VARIABLE",
"unresolved_variable" },
154 {
"ERCET_UNDEFINED_NETCLASS",
"undefined_netclass" },
155 {
"ERCET_SIMULATION_MODEL",
"simulation_model_issue" },
156 {
"ERCET_WIRE_DANGLING",
"wire_dangling" },
157 {
"ERCET_LIB_SYMBOL_ISSUES",
"lib_symbol_issues" },
158 {
"ERCET_LIB_SYMBOL_MISMATCH",
"lib_symbol_mismatch" },
159 {
"ERCET_FOOTPRINT_LINK_ISSUES",
"footprint_link_issues" },
160 {
"ERCET_FOOTPRINT_FILTERS",
"footprint_filter" },
161 {
"ERCET_UNANNOTATED",
"unannotated" },
162 {
"ERCET_EXTRA_UNITS",
"extra_units" },
163 {
"ERCET_DIFFERENT_UNIT_VALUE",
"unit_value_mismatch" },
164 {
"ERCET_DUPLICATE_REFERENCE",
"duplicate_reference" },
165 {
"ERCET_BUS_ENTRY_NEEDED",
"bus_entry_needed" },
166 {
"ERCET_FOUR_WAY_JUNCTION",
"four_way_junction" },
167 {
"ERCET_LABEL_MULTIPLE_WIRES",
"label_multiple_wires" },
168 {
"ERCET_UNCONNECTED_WIRE_ENDPOINT",
"unconnected_wire_endpoint" },
169 {
"ERCET_STACKED_PIN_SYNTAX",
"stacked_pin_name",
true },
170 {
"ERCET_PIN_MAP_BAD_PAD",
nullptr },
171 {
"ERCET_PIN_MAP_UNMAPPED_PIN",
nullptr },
172 {
"ERCET_PIN_MAP_DUPLICATE_PAD",
nullptr },
173 {
"ERCET_PIN_MAP_STALE_PIN",
nullptr },
174 {
"ERCET_EMPTY_LABEL_NAME",
nullptr },
175 {
"ERCET_VARIANT_SYMBOL_INVALID",
nullptr },
176 {
"ERCET_VARIANT_SYMBOL_INCOMPATIBLE",
nullptr },
177 {
"ERCET_DUPLICATE_PIN_ERROR",
"duplicate_pins" },
178 {
"ERCET_PIN_TO_PIN_WARNING",
"pin_to_pin" },
179 {
"ERCET_PIN_TO_PIN_ERROR",
"pin_to_pin" },
180 {
"ERCET_ANNOTATION_ACTION",
nullptr },
181 {
"ERCET_GENERIC_WARNING",
"generic-warning" },
182 {
"ERCET_GENERIC_ERROR",
"generic-error" },
183 {
"ERCET_FIELD_NAME_WHITESPACE",
"field_name_whitespace",
true },
184 {
"ERCET_WIRED_IMPLICIT_POWER",
nullptr },
188bool isUuid(
const std::string& aText )
190 if( aText.size() != 36 )
193 for(
size_t i = 0; i < aText.size(); i++ )
195 if( i == 8 || i == 13 || i == 18 || i == 23 )
197 if( aText[i] !=
'-' )
200 else if( !std::isxdigit(
static_cast<unsigned char>( aText[i] ) ) )
210bool parseCoordinate(
const nlohmann::json& aValue, std::string& aText, int64_t aDivisor = 1 )
212 if( !aValue.is_number_integer() && !aValue.is_string() )
215 std::string
text = aValue.is_string() ? aValue.get<std::string>() : aValue.
dump();
217 auto parsed = std::from_chars(
text.data(),
text.data() +
text.size(), value );
219 if( parsed.ec != std::errc() || parsed.ptr !=
text.data() +
text.size() || value % aDivisor != 0
220 || value / aDivisor < std::numeric_limits<int32_t>::min()
221 || value / aDivisor > std::numeric_limits<int32_t>::max() )
226 aText = std::to_string( value / aDivisor );
231std::string legacyLayerName(
const nlohmann::json& aLayer,
bool aPreTen )
234 static constexpr const char* layers[] = {
235 nullptr,
nullptr,
nullptr,
"F.Cu",
"In1.Cu",
"In2.Cu",
"In3.Cu",
"In4.Cu",
"In5.Cu",
236 "In6.Cu",
"In7.Cu",
"In8.Cu",
"In9.Cu",
"In10.Cu",
"In11.Cu",
"In12.Cu",
"In13.Cu",
"In14.Cu",
237 "In15.Cu",
"In16.Cu",
"In17.Cu",
"In18.Cu",
"In19.Cu",
"In20.Cu",
"In21.Cu",
"In22.Cu",
"In23.Cu",
238 "In24.Cu",
"In25.Cu",
"In26.Cu",
"In27.Cu",
"In28.Cu",
"In29.Cu",
"In30.Cu",
"B.Cu",
"B.Adhes",
239 "F.Adhes",
"B.Paste",
"F.Paste",
"B.SilkS",
"F.SilkS",
"B.Mask",
"F.Mask",
"Dwgs.User",
"Cmts.User",
240 "Eco1.User",
"Eco2.User",
"Edge.Cuts",
"Margin",
"B.CrtYd",
"F.CrtYd",
"B.Fab",
"F.Fab",
"User.1",
241 "User.2",
"User.3",
"User.4",
"User.5",
"User.6",
"User.7",
"User.8",
"User.9",
nullptr,
242 "User.10",
"User.11",
"User.12",
"User.13",
"User.14",
"User.15",
"User.16",
"User.17",
"User.18",
243 "User.19",
"User.20",
"User.21",
"User.22",
"User.23",
"User.24",
"User.25",
"User.26",
"User.27",
244 "User.28",
"User.29",
"User.30",
"User.31",
"User.32",
"User.33",
"User.34",
"User.35",
"User.36",
245 "User.37",
"User.38",
"User.39",
"User.40",
"User.41",
"User.42",
"User.43",
"User.44",
"User.45"
250 if( aLayer.is_number_integer() && aLayer >= 0 && aLayer < std::size( layers ) )
251 index = aLayer.get<
int>();
252 else if( aLayer.is_string() )
254 std::string
name = aLayer.get<std::string>();
256 for(
size_t i = 3; i < std::size( layers ); i++ )
261 std::string proto =
"BL_" + std::string( layers[i] );
262 std::replace( proto.begin(), proto.end(),
'.',
'_' );
265 index =
static_cast<int>( i );
272 return layers[
index];
276bool downgradeProtoExclusion(
const nlohmann::json& aEntry,
bool aPreTen, nlohmann::json& aResult )
278 if( !aEntry.is_object() || !aEntry.contains(
"marker" ) || !aEntry[
"marker"].is_object()
279 || ( aEntry.contains(
"comment" ) && !aEntry[
"comment"].is_string() ) )
284 const nlohmann::json& marker = aEntry[
"marker"];
286 if( !marker.contains(
"error_type" ) )
289 const LEGACY_DRC_ERROR* error =
nullptr;
290 const nlohmann::json& type = marker[
"error_type"];
292 if( type.is_string() )
294 for(
const LEGACY_DRC_ERROR& candidate : legacyDrcErrors )
296 if( type == candidate.m_protoName )
300 else if( type.is_number_integer() && type >= 0 && type < std::size( legacyDrcErrors ) )
302 error = &legacyDrcErrors[type.get<
size_t>()];
305 if( !error || !error->m_settingsKey || ( aPreTen && error->m_requiresTen ) )
311 if( marker.contains(
"position" ) )
313 const nlohmann::json& position = marker[
"position"];
315 if( !position.is_object() || ( position.contains(
"x_nm" ) && !parseCoordinate( position[
"x_nm"], x ) )
316 || ( position.contains(
"y_nm" ) && !parseCoordinate( position[
"y_nm"], y ) ) )
322 std::vector<std::string> ids;
324 if( marker.contains(
"items" ) )
326 if( !marker[
"items"].is_array() || marker[
"items"].size() > 2 )
329 for(
const nlohmann::json& item : marker[
"items"] )
331 if( !item.is_object() || !item.contains(
"value" ) || !item[
"value"].is_string()
332 || !isUuid( item[
"value"].get<std::string>() ) )
337 ids.push_back( item[
"value"].get<std::string>() );
341 const std::string nil =
"00000000-0000-0000-0000-000000000000";
342 std::string
main = ids.empty() ? nil : ids[0];
343 std::string aux = ids.size() < 2 ? nil : ids[1];
344 std::string key = error->m_settingsKey;
345 std::string value = key +
'|' + x +
'|' + y +
'|';
347 if( key ==
"copper_sliver" || key ==
"generic_warning" || key ==
"generic_error" || key ==
"starved_thermal"
348 || ( key ==
"unconnected_items" && !aPreTen ) )
350 std::string layer = marker.contains(
"layer" ) ? legacyLayerName( marker[
"layer"], aPreTen ) :
"";
355 if( key ==
"unconnected_items" )
356 value += layer +
"|4|" +
main +
'|' + aux;
357 else if( key ==
"starved_thermal" )
358 value +=
main +
'|' + aux +
'|' + layer;
364 value +=
main +
'|' + layer;
371 if( key ==
"unresolved_variable" && ids.empty() )
374 value +=
main +
'|' + aux;
377 aResult = nlohmann::json::array( { value, aEntry.value(
"comment",
"" ) } );
382bool downgradeLegacyExclusion(
const nlohmann::json& aEntry,
bool aPreTen, nlohmann::json& aResult )
387 if( aEntry.is_string() )
388 marker = aEntry.get<std::string>();
389 else if( aEntry.is_array() && !aEntry.empty() && aEntry[0].is_string()
390 && ( aEntry.size() == 1 || ( aEntry.size() == 2 && aEntry[1].is_string() ) ) )
392 marker = aEntry[0].get<std::string>();
394 if( aEntry.size() == 2 )
395 comment = aEntry[1].get<std::string>();
400 std::vector<std::string> parts;
403 for(
size_t end = marker.find(
'|' );
end != std::string::npos;
end = marker.find(
'|', start ) )
405 parts.push_back( marker.substr( start,
end - start ) );
409 parts.push_back( marker.substr( start ) );
411 if( parts.size() < 5 )
414 bool supported = parts[0] ==
"hole_near_hole";
416 for(
const LEGACY_DRC_ERROR& error : legacyDrcErrors )
418 if( error.m_settingsKey && parts[0] == error.m_settingsKey && !( aPreTen && error.m_requiresTen ) )
422 std::string coordinate;
424 if( !supported || !parseCoordinate( parts[1], coordinate ) || !parseCoordinate( parts[2], coordinate ) )
427 if( parts[0] ==
"unconnected_items" && parts.size() == 7 )
429 if( !isUuid( parts[5] ) || !isUuid( parts[6] ) )
433 marker = parts[0] +
'|' + parts[1] +
'|' + parts[2] +
'|' + parts[5] +
'|' + parts[6];
435 else if( parts[0] ==
"starved_thermal" )
437 if( parts.size() != 6 || !isUuid( parts[3] ) || !isUuid( parts[4] ) )
440 else if( parts[0] ==
"copper_sliver" || parts[0] ==
"generic_warning" || parts[0] ==
"generic_error" )
442 if( parts.size() != 5 || !isUuid( parts[3] ) )
445 else if( parts.size() != 5
446 || !( ( isUuid( parts[3] ) && isUuid( parts[4] ) )
447 || ( parts[0] ==
"unresolved_variable" && parts[3].
empty() && parts[4].
empty() ) ) )
452 aResult = nlohmann::json::array( { marker, comment } );
457bool legacyErcPath(
const nlohmann::json& aPath, std::string& aText )
459 if( !aPath.is_object() || !aPath.contains(
"path" ) || !aPath[
"path"].is_array() || aPath[
"path"].empty() )
462 for(
const nlohmann::json& part : aPath[
"path"] )
464 if( !part.is_object() || !part.contains(
"value" ) || !part[
"value"].is_string()
465 || !isUuid( part[
"value"].get<std::string>() ) )
470 aText +=
'/' + part[
"value"].get<std::string>();
477bool ercChildNeedsTextDowngrade(
const wxString& aText,
const nlohmann::json& aDoc,
bool aPreTen,
478 std::set<std::string>& aVisiting )
480 bool changed = aPreTen && ( aText.Contains( wxT(
"\\${" ) ) || aText.Contains( wxT(
"\\@{" ) ) );
481 std::function<bool( wxString* )> scan = [&]( wxString* aToken )
483 const wxString token = aToken->AfterLast(
':' );
484 changed |= token.StartsWith( wxT(
"PROPERTY." ) ) || token == wxT(
"SYMBOL_IS_POWER" )
485 || token == wxT(
"SYMBOL_IS_LOCAL_POWER" );
487 if( aDoc.contains(
"text_variables" ) && aDoc[
"text_variables"].is_object() )
489 const nlohmann::json& variables = aDoc[
"text_variables"];
490 const std::string key = aToken->ToStdString( wxConvUTF8 );
492 if( variables.contains( key ) && variables[key].is_string() && aVisiting.insert( key ).second )
494 changed |= ercChildNeedsTextDowngrade( wxString::FromUTF8( variables[key].get<std::string>() ), aDoc,
495 aPreTen, aVisiting );
496 aVisiting.erase( key );
504 return changed || ( aPreTen && scanned.Contains( wxT(
"@{" ) ) );
508bool downgradeProtoErcExclusion(
const nlohmann::json& aEntry,
const nlohmann::json& aDoc,
bool aPreTen,
509 nlohmann::json& aResult )
511 if( !aEntry.contains(
"marker" ) || !aEntry[
"marker"].is_object()
512 || ( aEntry.contains(
"comment" ) && !aEntry[
"comment"].is_string() ) )
517 const nlohmann::json& marker = aEntry[
"marker"];
519 if( !marker.contains(
"error_type" ) )
522 const LEGACY_DRC_ERROR* error =
nullptr;
523 const nlohmann::json& type = marker[
"error_type"];
525 if( type.is_string() )
527 for(
const LEGACY_DRC_ERROR& candidate : legacyErcErrors )
529 if( type == candidate.m_protoName )
533 else if( type.is_number_integer() && type >= 0 && type < std::size( legacyErcErrors ) )
534 error = &legacyErcErrors[type.get<
size_t>()];
536 if( !error || !error->m_settingsKey || ( aPreTen && error->m_requiresTen ) )
542 if( marker.contains(
"position" ) )
544 const nlohmann::json& position = marker[
"position"];
547 if( !position.is_object() || ( position.contains(
"x_nm" ) && !parseCoordinate( position[
"x_nm"], x, 100 ) )
548 || ( position.contains(
"y_nm" ) && !parseCoordinate( position[
"y_nm"], y, 100 ) ) )
554 const std::string nil =
"00000000-0000-0000-0000-000000000000";
555 std::vector<std::string> ids;
557 if( marker.contains(
"items" ) )
559 if( !marker[
"items"].is_array() || marker[
"items"].size() > 2 )
562 for(
const nlohmann::json& item : marker[
"items"] )
564 if( !item.is_object() || !item.contains(
"value" ) || !item[
"value"].is_string()
565 || !isUuid( item[
"value"].get<std::string>() ) )
570 ids.push_back( item[
"value"].get<std::string>() );
574 std::string
main = ids.empty() ? nil : ids[0];
575 std::string aux = ids.size() < 2 ? nil : ids[1];
576 std::string key = error->m_settingsKey;
578 if( marker.contains(
"child" ) )
580 const nlohmann::json& child = marker[
"child"];
582 if( ( key !=
"generic-warning" && key !=
"generic-error" && key !=
"unresolved_variable" ) || ids.empty()
583 ||
main == nil || !child.is_object() || !child.contains(
"text_value" )
584 || !child[
"text_value"].is_string() )
589 aux = child[
"text_value"].get<std::string>();
591 std::set<std::string> visiting;
593 if( aux.empty() || aux.find(
'|' ) != std::string::npos || marker.contains(
"aux_item_sheet_path" )
594 || ercChildNeedsTextDowngrade( wxString::FromUTF8( aux ), aDoc, aPreTen, visiting ) )
598 std::string value = key +
'|' + x +
'|' + y +
'|' +
main +
'|' + aux;
600 if( marker.contains(
"aux_item_sheet_path" ) && !marker.contains(
"main_item_sheet_path" ) )
603 for(
const char*
name : {
"sheet_specific_path",
"main_item_sheet_path",
"aux_item_sheet_path" } )
607 if( marker.contains(
name ) && !legacyErcPath( marker[
name],
path ) )
613 aResult = nlohmann::json::array( { value, aEntry.value(
"comment",
"" ) } );
618bool downgradeLegacyErcExclusion(
const nlohmann::json& aEntry,
const nlohmann::json& aDoc,
bool aPreTen,
619 nlohmann::json& aResult )
624 if( aEntry.is_string() )
625 marker = aEntry.get<std::string>();
626 else if( aEntry.is_array() && !aEntry.empty() && aEntry[0].is_string()
627 && ( aEntry.size() == 1 || ( aEntry.size() == 2 && aEntry[1].is_string() ) ) )
629 marker = aEntry[0].get<std::string>();
631 if( aEntry.size() == 2 )
632 comment = aEntry[1].get<std::string>();
637 std::vector<std::string> parts;
640 for(
size_t end = marker.find(
'|' );
end != std::string::npos;
end = marker.find(
'|', start ) )
642 parts.push_back( marker.substr( start,
end - start ) );
646 parts.push_back( marker.substr( start ) );
648 if( parts.size() != 5 && parts.size() != 8 )
651 bool supported = aPreTen && parts[0] ==
"global_label_dangling";
653 for(
const LEGACY_DRC_ERROR& error : legacyErcErrors )
655 if( error.m_settingsKey && parts[0] == error.m_settingsKey && !( aPreTen && error.m_requiresTen ) )
659 std::string coordinate;
661 if( !supported || !parseCoordinate( parts[1], coordinate ) || !parseCoordinate( parts[2], coordinate )
662 || !isUuid( parts[3] ) )
667 bool childText = parts[0] ==
"generic-warning" || parts[0] ==
"generic-error" || parts[0] ==
"unresolved_variable";
669 if( !childText && !isUuid( parts[4] ) )
672 std::set<std::string> visiting;
674 if( childText && ercChildNeedsTextDowngrade( wxString::FromUTF8( parts[4] ), aDoc, aPreTen, visiting ) )
677 aResult = nlohmann::json::array( { marker, comment } );
684 if( !aDoc.contains(
"erc" ) || !aDoc[
"erc"].is_object() )
687 nlohmann::json& settings = aDoc[
"erc"];
689 if( settings.contains(
"erc_exclusions" ) && !settings[
"erc_exclusions"].is_array() )
691 if( !settings[
"erc_exclusions"].is_null() && !settings[
"erc_exclusions"].
empty() )
693 _(
"The target cannot represent this exclusion. It is removed." ), 1 );
695 settings[
"erc_exclusions"] = nlohmann::json::array();
698 if( settings.contains(
"erc_exclusions" ) && settings[
"erc_exclusions"].is_array() )
700 nlohmann::json exclusions = nlohmann::json::array();
702 for(
const nlohmann::json& entry : settings[
"erc_exclusions"] )
704 nlohmann::json converted;
706 bool ok = entry.is_object() ? downgradeProtoErcExclusion( entry, aDoc, aPreTen, converted )
707 : downgradeLegacyErcExclusion( entry, aDoc, aPreTen, converted );
710 exclusions.push_back( std::move( converted ) );
713 _(
"The target cannot represent this exclusion. It is removed." ), 1 );
716 settings[
"erc_exclusions"] = std::move( exclusions );
719 if( settings.contains(
"meta" ) && settings[
"meta"].is_object() )
720 settings[
"meta"][
"version"] = 0;
724bool hasNonDefaultSettings(
const nlohmann::json& aSettings,
const nlohmann::json& aDefaults )
726 if( aSettings == aDefaults )
729 if( !aSettings.is_object() )
730 return !aSettings.empty() && !aSettings.is_null();
732 for(
const auto& [key, value] : aSettings.items() )
734 if( !aDefaults.contains( key ) || value != aDefaults[key] )
742bool hasConfiguredProjectSettings(
const nlohmann::json& aSettings,
const nlohmann::json& aDefaults,
743 bool aSectionRoot =
true )
745 if( aSettings == aDefaults || aSettings.is_null() || aSettings.empty() )
748 if( !aSettings.is_object() || !aDefaults.is_object() )
751 for(
const auto& [key, value] : aSettings.items() )
753 if( aSectionRoot && key ==
"meta" )
756 if( aDefaults.contains( key ) )
758 if( hasConfiguredProjectSettings( value, aDefaults[key],
false ) )
761 else if( !value.is_null() && !value.empty() )
773 auto downgradePreset = [&]( nlohmann::json& aPreset )
775 if( !aPreset.is_object() )
778 if( aPreset.contains(
"filter_scope" ) && aPreset[
"filter_scope"] !=
"reference"
779 && aPreset.contains(
"filter_string" ) && aPreset[
"filter_string"].is_string()
780 && !aPreset[
"filter_string"].get<std::string>().empty() )
783 _(
"The target can only filter references. The field filter is removed." ), 1 );
784 aPreset[
"filter_string"] =
"";
787 aPreset.erase(
"filter_scope" );
790 auto downgradeFormat = [&]( nlohmann::json& aFormat )
792 if( !aFormat.is_object() )
795 if( aFormat.contains(
"include_byte_order_mark" ) && aFormat[
"include_byte_order_mark"] ==
true )
797 _(
"The target cannot retain the BOM byte order mark option." ), 1 );
799 aFormat.erase(
"include_byte_order_mark" );
802 if( aSettings.contains(
"bom_settings" ) )
803 downgradePreset( aSettings[
"bom_settings"] );
805 if( aSettings.contains(
"bom_presets" ) && aSettings[
"bom_presets"].is_array() )
807 for( nlohmann::json& preset : aSettings[
"bom_presets"] )
808 downgradePreset( preset );
811 if( aSettings.contains(
"bom_fmt_settings" ) )
812 downgradeFormat( aSettings[
"bom_fmt_settings"] );
814 if( aSettings.contains(
"bom_fmt_presets" ) && aSettings[
"bom_fmt_presets"].is_array() )
816 for( nlohmann::json& format : aSettings[
"bom_fmt_presets"] )
817 downgradeFormat( format );
824 if( aDoc.contains(
"schematic" ) && aDoc[
"schematic"].is_object() )
825 downgradeBomSettings( aDoc[
"schematic"], aReport );
827 if( !aDoc.contains(
"board" ) || !aDoc[
"board"].is_object() )
830 nlohmann::json& board = aDoc[
"board"];
832 if( board.contains(
"ipc2581" ) && board[
"ipc2581"].is_object() )
834 nlohmann::json& ipc = board[
"ipc2581"];
835 bool changed =
false;
836 const nlohmann::json defaults = {
837 {
"mode",
"" }, {
"sections",
"" }, {
"custom_sections",
false }, {
"net_names",
"" }, {
"ref_des",
"" }
840 for(
const auto& [key, value] : defaults.items() )
842 if( ipc.contains( key ) )
844 changed |= ipc[key] != value;
851 _(
"The target cannot retain the selected output sections or naming rules." ), 1 );
854 if( board.contains(
"idf_export" ) )
856 const nlohmann::json defaults = { {
"units", 0 },
857 {
"origin_mode", 2 },
858 {
"user_origin_x", 0.0 },
859 {
"user_origin_y", 0.0 },
860 {
"include_unspecified",
true },
861 {
"include_dnp",
true },
862 {
"calculate_height_from_models",
true },
863 {
"part_number_field",
"Value" } };
865 if( hasNonDefaultSettings( board[
"idf_export"], defaults ) )
867 _(
"The target cannot retain the selected IDF export settings." ), 1 );
869 board.erase(
"idf_export" );
872 if( board.contains(
"design_settings" ) && board[
"design_settings"].is_object() )
874 nlohmann::json& settings = board[
"design_settings"];
875 const nlohmann::json defaults = { {
"bom_export_filename",
"${PROJECTNAME}.csv" },
876 {
"bom_settings", BOM_PRESET::DefaultEditing() },
878 {
"bom_presets", nlohmann::json::array() },
879 {
"bom_fmt_presets", nlohmann::json::array() } };
880 bool changed =
false;
882 for(
const auto& [key, value] : defaults.items() )
884 if( !settings.contains( key ) )
887 changed |= hasNonDefaultSettings( settings[key], value );
888 settings.erase( key );
893 _(
"The target cannot retain the PCB BOM export settings." ), 1 );
900 if( !aDoc.contains(
"board" ) || !aDoc[
"board"].is_object() || !aDoc[
"board"].contains(
"design_settings" )
901 || !aDoc[
"board"][
"design_settings"].is_object() )
906 nlohmann::json& settings = aDoc[
"board"][
"design_settings"];
908 if( settings.contains(
"drc_exclusions" ) )
910 nlohmann::json exclusions = nlohmann::json::array();
912 if( !settings[
"drc_exclusions"].is_array() )
914 if( !settings[
"drc_exclusions"].is_null() && !settings[
"drc_exclusions"].
empty() )
916 _(
"The target cannot represent this exclusion. It is removed." ), 1 );
920 for(
const nlohmann::json& entry : settings[
"drc_exclusions"] )
922 nlohmann::json converted;
923 bool ok = entry.is_object() ? downgradeProtoExclusion( entry, aPreTen, converted )
924 : downgradeLegacyExclusion( entry, aPreTen, converted );
927 exclusions.push_back( std::move( converted ) );
930 _(
"The target cannot represent this exclusion. It is removed." ), 1 );
934 settings[
"drc_exclusions"] = std::move( exclusions );
937 if( settings.contains(
"via_stack_presets" ) )
939 if( !settings[
"via_stack_presets"].
empty() && !settings[
"via_stack_presets"].is_null() )
943 settings.erase(
"via_stack_presets" );
946 if( settings.contains(
"teardrop_parameters" ) && settings[
"teardrop_parameters"].is_array() )
948 for( nlohmann::json& entry : settings[
"teardrop_parameters"] )
950 if( !entry.is_object() )
953 if( entry.contains(
"td_enabled" ) && entry[
"td_enabled"] ==
true )
955 _(
"The target cannot retain enabled teardrop defaults." ), 1 );
957 entry.erase(
"td_enabled" );
961 if( settings.contains(
"meta" ) && settings[
"meta"].is_object() )
962 settings[
"meta"][
"version"] = 2;
969 auto knownSchema = [&](
const nlohmann::json& aSettings,
int aMaximum,
const wxString& aName )
971 if( aSettings.is_object() && aSettings.contains(
"meta" ) && aSettings[
"meta"].is_object()
972 && aSettings[
"meta"].contains(
"version" ) && aSettings[
"meta"][
"version"].is_number()
973 && aSettings[
"meta"][
"version"] > aMaximum )
976 wxString::Format(
_(
"The %s schema is newer than the exporter has reviewed." ), aName ) );
983 bool known = knownSchema( aDoc, 4,
_(
"project" ) );
985 if( aDoc.contains(
"net_settings" ) )
986 known &= knownSchema( aDoc[
"net_settings"], 5,
_(
"net settings" ) );
988 if( aDoc.contains(
"tuning_profiles" ) )
989 known &= knownSchema( aDoc[
"tuning_profiles"], 2,
_(
"tuning profiles" ) );
991 if( aDoc.contains(
"component_class_settings" ) )
992 known &= knownSchema( aDoc[
"component_class_settings"], 0,
_(
"component class settings" ) );
994 if( aDoc.contains(
"erc" ) )
995 known &= knownSchema( aDoc[
"erc"], 1,
_(
"ERC settings" ) );
997 if( aDoc.contains(
"board" ) && aDoc[
"board"].is_object() && aDoc[
"board"].contains(
"design_settings" ) )
998 known &= knownSchema( aDoc[
"board"][
"design_settings"], 3,
_(
"board design settings" ) );
1005 bool tuningLoss =
false;
1007 downgradeBoardSettings( aDoc, preTen, aReport );
1008 downgradeErcSettings( aDoc, preTen, aReport );
1009 downgradeOutputSettings( aDoc, aReport );
1011 if( aDoc.contains(
"net_settings" ) )
1013 nlohmann::json& net = aDoc[
"net_settings"];
1015 if( net.contains(
"net_chain_netclasses" ) )
1017 if( !net[
"net_chain_netclasses"].
empty() && !net[
"net_chain_netclasses"].is_null() )
1019 _(
"Removed from the project file." ), 1 );
1021 net.erase(
"net_chain_netclasses" );
1024 if( net.contains(
"net_chain_classes" ) )
1026 bool empty = net[
"net_chain_classes"].is_null() || net[
"net_chain_classes"].empty();
1028 net.erase(
"net_chain_classes" );
1039 if( net.contains(
"classes" ) && net[
"classes"].is_array() )
1041 for( nlohmann::json& netclass : net[
"classes"] )
1043 if( netclass.is_object() )
1045 if( netclass.contains(
"tuning_profile" ) && netclass[
"tuning_profile"].is_string()
1046 && !netclass[
"tuning_profile"].get_ref<
const std::string&>().empty() )
1051 netclass.erase(
"tuning_profile" );
1056 if( net.contains(
"meta" ) && net[
"meta"].is_object() )
1057 net[
"meta"][
"version"] = 4;
1061 if( aDoc.contains(
"tuning_profiles" ) )
1065 const nlohmann::json defaults = {
1066 {
"tuning_profiles_impedance_geometric", nlohmann::json::array() },
1068 tuningLoss |= hasConfiguredProjectSettings( aDoc[
"tuning_profiles"], defaults );
1069 aDoc.erase(
"tuning_profiles" );
1073 nlohmann::json& profiles = aDoc[
"tuning_profiles"];
1075 if( profiles.contains(
"tuning_profiles_impedance_geometric" )
1076 && profiles[
"tuning_profiles_impedance_geometric"].is_array() )
1078 for( nlohmann::json& entry : profiles[
"tuning_profiles_impedance_geometric"] )
1080 if( !entry.is_object() )
1083 if( entry.contains(
"frequency" ) && entry[
"frequency"].is_number()
1084 && entry[
"frequency"].get<
double>() != 1e9 )
1087 _(
"The target uses its default calculation frequency." ), 1 );
1090 if( entry.contains(
"model_solder_mask" ) && entry[
"model_solder_mask"].is_boolean()
1091 && entry[
"model_solder_mask"].get<
bool>() )
1094 _(
"The target cannot include solder mask in the calculation." ), 1 );
1097 entry.erase(
"frequency" );
1098 entry.erase(
"model_solder_mask" );
1100 if( entry.contains(
"net_chain_bridge_prop_delay" )
1101 && entry[
"net_chain_bridge_prop_delay"].is_number()
1102 && entry[
"net_chain_bridge_prop_delay"].get<
double>() != 0 )
1105 _(
"The target cannot model delay across net chain bridges." ), 1 );
1108 entry.erase(
"net_chain_bridge_prop_delay" );
1112 if( profiles.contains(
"meta" ) && profiles[
"meta"].is_object() )
1113 profiles[
"meta"][
"version"] = 0;
1120 _(
"Removed saved profiles or netclass profile selections from the project file." ), 1 );
1126 if( aDoc.contains(
"component_class_settings" ) )
1128 const nlohmann::json defaults = {
1129 {
"assignments", nlohmann::json::array() },
1130 {
"sheet_component_classes", { {
"enabled",
false } } },
1132 bool configured = hasConfiguredProjectSettings( aDoc[
"component_class_settings"], defaults );
1133 aDoc.erase(
"component_class_settings" );
1138 _(
"Removed from the project file." ), 1 );
1142 if( aDoc.contains(
"schematic" ) && aDoc[
"schematic"].is_object() )
1145 aDoc[
"schematic"].erase(
"top_level_sheets" );
1148 aDoc[
"schematic"].erase(
"bus_aliases" );
1151 aDoc[
"schematic"].erase(
"variants" );
1156 if( aDoc.contains(
"meta" ) && aDoc[
"meta"].is_object() )
1157 aDoc[
"meta"][
"version"] = 3;
What an export to a given target will keep, approximate, drop, or block.
void Add(DOWNGRADE_BUCKET aBucket, const wxString &aFeature, const wxString &aDetail=wxEmptyString, int aCount=1)
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
@ BLOCK
Cannot be represented at all, export to this target is not possible.
@ DROP
No equivalent, left out.
static bool empty(const wxTextEntryBase *aCtrl)
void DowngradeProjectFileJson(nlohmann::json &aDoc, const DOWNGRADE_TARGET &aTarget, COMPATIBILITY_REPORT &aReport)
Migrate a .kicad_pro document down to what the target release expects.
static BOM_FMT_PRESET CSV()
An older KiCad release we can export a project back to.
int m_boardVersion
SEXPR_BOARD_FILE_VERSION for the target.
wxString dump(const wxArrayString &aArray)
Debug helper for printing wxArrayString contents.