KiCad PCB EDA Suite
Loading...
Searching...
No Matches
project_file_downgrade.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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
21#include <common.h>
23
24#include <nlohmann/json.hpp>
25
26#include <algorithm>
27#include <cctype>
28#include <charconv>
29#include <cstdint>
30#include <functional>
31#include <iterator>
32#include <limits>
33#include <set>
34#include <string>
35#include <vector>
36
37#include <wx/intl.h>
38
39
40namespace
41{
42struct LEGACY_DRC_ERROR
43{
44 const char* m_protoName;
45 const char* m_settingsKey;
46 bool m_requiresTen = false;
47};
48
49// Frozen 9.0 and 10.0 DRC_ITEM keys, ordered by the protobuf DrcErrorType values.
50// The current error names cannot be lowercased to recover these legacy identifiers.
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 },
120};
121
122
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 },
185};
186
187
188bool isUuid( const std::string& aText )
189{
190 if( aText.size() != 36 )
191 return false;
192
193 for( size_t i = 0; i < aText.size(); i++ )
194 {
195 if( i == 8 || i == 13 || i == 18 || i == 23 )
196 {
197 if( aText[i] != '-' )
198 return false;
199 }
200 else if( !std::isxdigit( static_cast<unsigned char>( aText[i] ) ) )
201 {
202 return false;
203 }
204 }
205
206 return true;
207}
208
209
210bool parseCoordinate( const nlohmann::json& aValue, std::string& aText, int64_t aDivisor = 1 )
211{
212 if( !aValue.is_number_integer() && !aValue.is_string() )
213 return false;
214
215 std::string text = aValue.is_string() ? aValue.get<std::string>() : aValue.dump();
216 int64_t value = 0;
217 auto parsed = std::from_chars( text.data(), text.data() + text.size(), value );
218
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() )
222 {
223 return false;
224 }
225
226 aText = std::to_string( value / aDivisor );
227 return true;
228}
229
230
231std::string legacyLayerName( const nlohmann::json& aLayer, bool aPreTen )
232{
233 // Protobuf accepts both its canonical enum strings and numeric enum values.
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"
246 };
247
248 int index = -1;
249
250 if( aLayer.is_number_integer() && aLayer >= 0 && aLayer < std::size( layers ) )
251 index = aLayer.get<int>();
252 else if( aLayer.is_string() )
253 {
254 std::string name = aLayer.get<std::string>();
255
256 for( size_t i = 3; i < std::size( layers ); i++ )
257 {
258 if( !layers[i] )
259 continue;
260
261 std::string proto = "BL_" + std::string( layers[i] );
262 std::replace( proto.begin(), proto.end(), '.', '_' );
263
264 if( name == proto )
265 index = static_cast<int>( i );
266 }
267 }
268
269 if( index < 3 || ( aPreTen && index > 61 ) || !layers[index] )
270 return {};
271
272 return layers[index];
273}
274
275
276bool downgradeProtoExclusion( const nlohmann::json& aEntry, bool aPreTen, nlohmann::json& aResult )
277{
278 if( !aEntry.is_object() || !aEntry.contains( "marker" ) || !aEntry["marker"].is_object()
279 || ( aEntry.contains( "comment" ) && !aEntry["comment"].is_string() ) )
280 {
281 return false;
282 }
283
284 const nlohmann::json& marker = aEntry["marker"];
285
286 if( !marker.contains( "error_type" ) )
287 return false;
288
289 const LEGACY_DRC_ERROR* error = nullptr;
290 const nlohmann::json& type = marker["error_type"];
291
292 if( type.is_string() )
293 {
294 for( const LEGACY_DRC_ERROR& candidate : legacyDrcErrors )
295 {
296 if( type == candidate.m_protoName )
297 error = &candidate;
298 }
299 }
300 else if( type.is_number_integer() && type >= 0 && type < std::size( legacyDrcErrors ) )
301 {
302 error = &legacyDrcErrors[type.get<size_t>()];
303 }
304
305 if( !error || !error->m_settingsKey || ( aPreTen && error->m_requiresTen ) )
306 return false;
307
308 std::string x = "0";
309 std::string y = "0";
310
311 if( marker.contains( "position" ) )
312 {
313 const nlohmann::json& position = marker["position"];
314
315 if( !position.is_object() || ( position.contains( "x_nm" ) && !parseCoordinate( position["x_nm"], x ) )
316 || ( position.contains( "y_nm" ) && !parseCoordinate( position["y_nm"], y ) ) )
317 {
318 return false;
319 }
320 }
321
322 std::vector<std::string> ids;
323
324 if( marker.contains( "items" ) )
325 {
326 if( !marker["items"].is_array() || marker["items"].size() > 2 )
327 return false;
328
329 for( const nlohmann::json& item : marker["items"] )
330 {
331 if( !item.is_object() || !item.contains( "value" ) || !item["value"].is_string()
332 || !isUuid( item["value"].get<std::string>() ) )
333 {
334 return false;
335 }
336
337 ids.push_back( item["value"].get<std::string>() );
338 }
339 }
340
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 + '|';
346
347 if( key == "copper_sliver" || key == "generic_warning" || key == "generic_error" || key == "starved_thermal"
348 || ( key == "unconnected_items" && !aPreTen ) )
349 {
350 std::string layer = marker.contains( "layer" ) ? legacyLayerName( marker["layer"], aPreTen ) : "";
351
352 if( layer.empty() )
353 return false;
354
355 if( key == "unconnected_items" )
356 value += layer + "|4|" + main + '|' + aux;
357 else if( key == "starved_thermal" )
358 value += main + '|' + aux + '|' + layer;
359 else
360 {
361 if( ids.size() > 1 )
362 return false;
363
364 value += main + '|' + layer;
365 }
366 }
367 else
368 {
369 // The ProtoJSON form cannot distinguish a drawing-sheet unresolved variable from
370 // an item-less board marker. The old empty-ID representation would change its type.
371 if( key == "unresolved_variable" && ids.empty() )
372 return false;
373
374 value += main + '|' + aux;
375 }
376
377 aResult = nlohmann::json::array( { value, aEntry.value( "comment", "" ) } );
378 return true;
379}
380
381
382bool downgradeLegacyExclusion( const nlohmann::json& aEntry, bool aPreTen, nlohmann::json& aResult )
383{
384 std::string marker;
385 std::string comment;
386
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() ) ) )
391 {
392 marker = aEntry[0].get<std::string>();
393
394 if( aEntry.size() == 2 )
395 comment = aEntry[1].get<std::string>();
396 }
397 else
398 return false;
399
400 std::vector<std::string> parts;
401 size_t start = 0;
402
403 for( size_t end = marker.find( '|' ); end != std::string::npos; end = marker.find( '|', start ) )
404 {
405 parts.push_back( marker.substr( start, end - start ) );
406 start = end + 1;
407 }
408
409 parts.push_back( marker.substr( start ) );
410
411 if( parts.size() < 5 )
412 return false;
413
414 bool supported = parts[0] == "hole_near_hole";
415
416 for( const LEGACY_DRC_ERROR& error : legacyDrcErrors )
417 {
418 if( error.m_settingsKey && parts[0] == error.m_settingsKey && !( aPreTen && error.m_requiresTen ) )
419 supported = true;
420 }
421
422 std::string coordinate;
423
424 if( !supported || !parseCoordinate( parts[1], coordinate ) || !parseCoordinate( parts[2], coordinate ) )
425 return false;
426
427 if( parts[0] == "unconnected_items" && parts.size() == 7 )
428 {
429 if( !isUuid( parts[5] ) || !isUuid( parts[6] ) )
430 return false;
431
432 if( aPreTen )
433 marker = parts[0] + '|' + parts[1] + '|' + parts[2] + '|' + parts[5] + '|' + parts[6];
434 }
435 else if( parts[0] == "starved_thermal" )
436 {
437 if( parts.size() != 6 || !isUuid( parts[3] ) || !isUuid( parts[4] ) )
438 return false;
439 }
440 else if( parts[0] == "copper_sliver" || parts[0] == "generic_warning" || parts[0] == "generic_error" )
441 {
442 if( parts.size() != 5 || !isUuid( parts[3] ) )
443 return false;
444 }
445 else if( parts.size() != 5
446 || !( ( isUuid( parts[3] ) && isUuid( parts[4] ) )
447 || ( parts[0] == "unresolved_variable" && parts[3].empty() && parts[4].empty() ) ) )
448 {
449 return false;
450 }
451
452 aResult = nlohmann::json::array( { marker, comment } );
453 return true;
454}
455
456
457bool legacyErcPath( const nlohmann::json& aPath, std::string& aText )
458{
459 if( !aPath.is_object() || !aPath.contains( "path" ) || !aPath["path"].is_array() || aPath["path"].empty() )
460 return false;
461
462 for( const nlohmann::json& part : aPath["path"] )
463 {
464 if( !part.is_object() || !part.contains( "value" ) || !part["value"].is_string()
465 || !isUuid( part["value"].get<std::string>() ) )
466 {
467 return false;
468 }
469
470 aText += '/' + part["value"].get<std::string>();
471 }
472
473 return true;
474}
475
476
477bool ercChildNeedsTextDowngrade( const wxString& aText, const nlohmann::json& aDoc, bool aPreTen,
478 std::set<std::string>& aVisiting )
479{
480 bool changed = aPreTen && ( aText.Contains( wxT( "\\${" ) ) || aText.Contains( wxT( "\\@{" ) ) );
481 std::function<bool( wxString* )> scan = [&]( wxString* aToken )
482 {
483 const wxString token = aToken->AfterLast( ':' );
484 changed |= token.StartsWith( wxT( "PROPERTY." ) ) || token == wxT( "SYMBOL_IS_POWER" )
485 || token == wxT( "SYMBOL_IS_LOCAL_POWER" );
486
487 if( aDoc.contains( "text_variables" ) && aDoc["text_variables"].is_object() )
488 {
489 const nlohmann::json& variables = aDoc["text_variables"];
490 const std::string key = aToken->ToStdString( wxConvUTF8 );
491
492 if( variables.contains( key ) && variables[key].is_string() && aVisiting.insert( key ).second )
493 {
494 changed |= ercChildNeedsTextDowngrade( wxString::FromUTF8( variables[key].get<std::string>() ), aDoc,
495 aPreTen, aVisiting );
496 aVisiting.erase( key );
497 }
498 }
499
500 return false;
501 };
502
503 const wxString scanned = ExpandTextVars( aText, &scan, INTERNAL );
504 return changed || ( aPreTen && scanned.Contains( wxT( "@{" ) ) );
505}
506
507
508bool downgradeProtoErcExclusion( const nlohmann::json& aEntry, const nlohmann::json& aDoc, bool aPreTen,
509 nlohmann::json& aResult )
510{
511 if( !aEntry.contains( "marker" ) || !aEntry["marker"].is_object()
512 || ( aEntry.contains( "comment" ) && !aEntry["comment"].is_string() ) )
513 {
514 return false;
515 }
516
517 const nlohmann::json& marker = aEntry["marker"];
518
519 if( !marker.contains( "error_type" ) )
520 return false;
521
522 const LEGACY_DRC_ERROR* error = nullptr;
523 const nlohmann::json& type = marker["error_type"];
524
525 if( type.is_string() )
526 {
527 for( const LEGACY_DRC_ERROR& candidate : legacyErcErrors )
528 {
529 if( type == candidate.m_protoName )
530 error = &candidate;
531 }
532 }
533 else if( type.is_number_integer() && type >= 0 && type < std::size( legacyErcErrors ) )
534 error = &legacyErcErrors[type.get<size_t>()];
535
536 if( !error || !error->m_settingsKey || ( aPreTen && error->m_requiresTen ) )
537 return false;
538
539 std::string x = "0";
540 std::string y = "0";
541
542 if( marker.contains( "position" ) )
543 {
544 const nlohmann::json& position = marker["position"];
545
546 // Schematic internal units are 100 nm. Non-integral conversions cannot keep identity.
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 ) ) )
549 {
550 return false;
551 }
552 }
553
554 const std::string nil = "00000000-0000-0000-0000-000000000000";
555 std::vector<std::string> ids;
556
557 if( marker.contains( "items" ) )
558 {
559 if( !marker["items"].is_array() || marker["items"].size() > 2 )
560 return false;
561
562 for( const nlohmann::json& item : marker["items"] )
563 {
564 if( !item.is_object() || !item.contains( "value" ) || !item["value"].is_string()
565 || !isUuid( item["value"].get<std::string>() ) )
566 {
567 return false;
568 }
569
570 ids.push_back( item["value"].get<std::string>() );
571 }
572 }
573
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;
577
578 if( marker.contains( "child" ) )
579 {
580 const nlohmann::json& child = marker["child"];
581
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() )
585 {
586 return false;
587 }
588
589 aux = child["text_value"].get<std::string>();
590
591 std::set<std::string> visiting;
592
593 if( aux.empty() || aux.find( '|' ) != std::string::npos || marker.contains( "aux_item_sheet_path" )
594 || ercChildNeedsTextDowngrade( wxString::FromUTF8( aux ), aDoc, aPreTen, visiting ) )
595 return false;
596 }
597
598 std::string value = key + '|' + x + '|' + y + '|' + main + '|' + aux;
599
600 if( marker.contains( "aux_item_sheet_path" ) && !marker.contains( "main_item_sheet_path" ) )
601 return false;
602
603 for( const char* name : { "sheet_specific_path", "main_item_sheet_path", "aux_item_sheet_path" } )
604 {
605 std::string path;
606
607 if( marker.contains( name ) && !legacyErcPath( marker[name], path ) )
608 return false;
609
610 value += '|' + path;
611 }
612
613 aResult = nlohmann::json::array( { value, aEntry.value( "comment", "" ) } );
614 return true;
615}
616
617
618bool downgradeLegacyErcExclusion( const nlohmann::json& aEntry, const nlohmann::json& aDoc, bool aPreTen,
619 nlohmann::json& aResult )
620{
621 std::string marker;
622 std::string comment;
623
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() ) ) )
628 {
629 marker = aEntry[0].get<std::string>();
630
631 if( aEntry.size() == 2 )
632 comment = aEntry[1].get<std::string>();
633 }
634 else
635 return false;
636
637 std::vector<std::string> parts;
638 size_t start = 0;
639
640 for( size_t end = marker.find( '|' ); end != std::string::npos; end = marker.find( '|', start ) )
641 {
642 parts.push_back( marker.substr( start, end - start ) );
643 start = end + 1;
644 }
645
646 parts.push_back( marker.substr( start ) );
647
648 if( parts.size() != 5 && parts.size() != 8 )
649 return false;
650
651 bool supported = aPreTen && parts[0] == "global_label_dangling";
652
653 for( const LEGACY_DRC_ERROR& error : legacyErcErrors )
654 {
655 if( error.m_settingsKey && parts[0] == error.m_settingsKey && !( aPreTen && error.m_requiresTen ) )
656 supported = true;
657 }
658
659 std::string coordinate;
660
661 if( !supported || !parseCoordinate( parts[1], coordinate ) || !parseCoordinate( parts[2], coordinate )
662 || !isUuid( parts[3] ) )
663 {
664 return false;
665 }
666
667 bool childText = parts[0] == "generic-warning" || parts[0] == "generic-error" || parts[0] == "unresolved_variable";
668
669 if( !childText && !isUuid( parts[4] ) )
670 return false;
671
672 std::set<std::string> visiting;
673
674 if( childText && ercChildNeedsTextDowngrade( wxString::FromUTF8( parts[4] ), aDoc, aPreTen, visiting ) )
675 return false;
676
677 aResult = nlohmann::json::array( { marker, comment } );
678 return true;
679}
680
681
682void downgradeErcSettings( nlohmann::json& aDoc, bool aPreTen, COMPATIBILITY_REPORT& aReport )
683{
684 if( !aDoc.contains( "erc" ) || !aDoc["erc"].is_object() )
685 return;
686
687 nlohmann::json& settings = aDoc["erc"];
688
689 if( settings.contains( "erc_exclusions" ) && !settings["erc_exclusions"].is_array() )
690 {
691 if( !settings["erc_exclusions"].is_null() && !settings["erc_exclusions"].empty() )
692 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "ERC exclusions" ),
693 _( "The target cannot represent this exclusion. It is removed." ), 1 );
694
695 settings["erc_exclusions"] = nlohmann::json::array();
696 }
697
698 if( settings.contains( "erc_exclusions" ) && settings["erc_exclusions"].is_array() )
699 {
700 nlohmann::json exclusions = nlohmann::json::array();
701
702 for( const nlohmann::json& entry : settings["erc_exclusions"] )
703 {
704 nlohmann::json converted;
705
706 bool ok = entry.is_object() ? downgradeProtoErcExclusion( entry, aDoc, aPreTen, converted )
707 : downgradeLegacyErcExclusion( entry, aDoc, aPreTen, converted );
708
709 if( ok )
710 exclusions.push_back( std::move( converted ) );
711 else
712 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "ERC exclusions" ),
713 _( "The target cannot represent this exclusion. It is removed." ), 1 );
714 }
715
716 settings["erc_exclusions"] = std::move( exclusions );
717 }
718
719 if( settings.contains( "meta" ) && settings["meta"].is_object() )
720 settings["meta"]["version"] = 0;
721}
722
723
724bool hasNonDefaultSettings( const nlohmann::json& aSettings, const nlohmann::json& aDefaults )
725{
726 if( aSettings == aDefaults )
727 return false;
728
729 if( !aSettings.is_object() )
730 return !aSettings.empty() && !aSettings.is_null();
731
732 for( const auto& [key, value] : aSettings.items() )
733 {
734 if( !aDefaults.contains( key ) || value != aDefaults[key] )
735 return true;
736 }
737
738 return false;
739}
740
741
742bool hasConfiguredProjectSettings( const nlohmann::json& aSettings, const nlohmann::json& aDefaults,
743 bool aSectionRoot = true )
744{
745 if( aSettings == aDefaults || aSettings.is_null() || aSettings.empty() )
746 return false;
747
748 if( !aSettings.is_object() || !aDefaults.is_object() )
749 return true;
750
751 for( const auto& [key, value] : aSettings.items() )
752 {
753 if( aSectionRoot && key == "meta" )
754 continue;
755
756 if( aDefaults.contains( key ) )
757 {
758 if( hasConfiguredProjectSettings( value, aDefaults[key], false ) )
759 return true;
760 }
761 else if( !value.is_null() && !value.empty() )
762 {
763 return true;
764 }
765 }
766
767 return false;
768}
769
770
771void downgradeBomSettings( nlohmann::json& aSettings, COMPATIBILITY_REPORT& aReport )
772{
773 auto downgradePreset = [&]( nlohmann::json& aPreset )
774 {
775 if( !aPreset.is_object() )
776 return;
777
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() )
781 {
782 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "BOM field filters" ),
783 _( "The target can only filter references. The field filter is removed." ), 1 );
784 aPreset["filter_string"] = "";
785 }
786
787 aPreset.erase( "filter_scope" );
788 };
789
790 auto downgradeFormat = [&]( nlohmann::json& aFormat )
791 {
792 if( !aFormat.is_object() )
793 return;
794
795 if( aFormat.contains( "include_byte_order_mark" ) && aFormat["include_byte_order_mark"] == true )
796 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "BOM byte order marks" ),
797 _( "The target cannot retain the BOM byte order mark option." ), 1 );
798
799 aFormat.erase( "include_byte_order_mark" );
800 };
801
802 if( aSettings.contains( "bom_settings" ) )
803 downgradePreset( aSettings["bom_settings"] );
804
805 if( aSettings.contains( "bom_presets" ) && aSettings["bom_presets"].is_array() )
806 {
807 for( nlohmann::json& preset : aSettings["bom_presets"] )
808 downgradePreset( preset );
809 }
810
811 if( aSettings.contains( "bom_fmt_settings" ) )
812 downgradeFormat( aSettings["bom_fmt_settings"] );
813
814 if( aSettings.contains( "bom_fmt_presets" ) && aSettings["bom_fmt_presets"].is_array() )
815 {
816 for( nlohmann::json& format : aSettings["bom_fmt_presets"] )
817 downgradeFormat( format );
818 }
819}
820
821
822void downgradeOutputSettings( nlohmann::json& aDoc, COMPATIBILITY_REPORT& aReport )
823{
824 if( aDoc.contains( "schematic" ) && aDoc["schematic"].is_object() )
825 downgradeBomSettings( aDoc["schematic"], aReport );
826
827 if( !aDoc.contains( "board" ) || !aDoc["board"].is_object() )
828 return;
829
830 nlohmann::json& board = aDoc["board"];
831
832 if( board.contains( "ipc2581" ) && board["ipc2581"].is_object() )
833 {
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", "" }
838 };
839
840 for( const auto& [key, value] : defaults.items() )
841 {
842 if( ipc.contains( key ) )
843 {
844 changed |= ipc[key] != value;
845 ipc.erase( key );
846 }
847 }
848
849 if( changed )
850 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "IPC-2581 output customization" ),
851 _( "The target cannot retain the selected output sections or naming rules." ), 1 );
852 }
853
854 if( board.contains( "idf_export" ) )
855 {
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" } };
864
865 if( hasNonDefaultSettings( board["idf_export"], defaults ) )
866 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "IDF output customization" ),
867 _( "The target cannot retain the selected IDF export settings." ), 1 );
868
869 board.erase( "idf_export" );
870 }
871
872 if( board.contains( "design_settings" ) && board["design_settings"].is_object() )
873 {
874 nlohmann::json& settings = board["design_settings"];
875 const nlohmann::json defaults = { { "bom_export_filename", "${PROJECTNAME}.csv" },
876 { "bom_settings", BOM_PRESET::DefaultEditing() },
877 { "bom_fmt_settings", BOM_FMT_PRESET::CSV() },
878 { "bom_presets", nlohmann::json::array() },
879 { "bom_fmt_presets", nlohmann::json::array() } };
880 bool changed = false;
881
882 for( const auto& [key, value] : defaults.items() )
883 {
884 if( !settings.contains( key ) )
885 continue;
886
887 changed |= hasNonDefaultSettings( settings[key], value );
888 settings.erase( key );
889 }
890
891 if( changed )
892 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "PCB BOM output customization" ),
893 _( "The target cannot retain the PCB BOM export settings." ), 1 );
894 }
895}
896
897
898void downgradeBoardSettings( nlohmann::json& aDoc, bool aPreTen, COMPATIBILITY_REPORT& aReport )
899{
900 if( !aDoc.contains( "board" ) || !aDoc["board"].is_object() || !aDoc["board"].contains( "design_settings" )
901 || !aDoc["board"]["design_settings"].is_object() )
902 {
903 return;
904 }
905
906 nlohmann::json& settings = aDoc["board"]["design_settings"];
907
908 if( settings.contains( "drc_exclusions" ) )
909 {
910 nlohmann::json exclusions = nlohmann::json::array();
911
912 if( !settings["drc_exclusions"].is_array() )
913 {
914 if( !settings["drc_exclusions"].is_null() && !settings["drc_exclusions"].empty() )
915 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "DRC exclusions" ),
916 _( "The target cannot represent this exclusion. It is removed." ), 1 );
917 }
918 else
919 {
920 for( const nlohmann::json& entry : settings["drc_exclusions"] )
921 {
922 nlohmann::json converted;
923 bool ok = entry.is_object() ? downgradeProtoExclusion( entry, aPreTen, converted )
924 : downgradeLegacyExclusion( entry, aPreTen, converted );
925
926 if( ok )
927 exclusions.push_back( std::move( converted ) );
928 else
929 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "DRC exclusions" ),
930 _( "The target cannot represent this exclusion. It is removed." ), 1 );
931 }
932 }
933
934 settings["drc_exclusions"] = std::move( exclusions );
935 }
936
937 if( settings.contains( "via_stack_presets" ) )
938 {
939 if( !settings["via_stack_presets"].empty() && !settings["via_stack_presets"].is_null() )
940 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "Microvia stack presets" ), _( "Removed from the project file." ),
941 1 );
942
943 settings.erase( "via_stack_presets" );
944 }
945
946 if( settings.contains( "teardrop_parameters" ) && settings["teardrop_parameters"].is_array() )
947 {
948 for( nlohmann::json& entry : settings["teardrop_parameters"] )
949 {
950 if( !entry.is_object() )
951 continue;
952
953 if( entry.contains( "td_enabled" ) && entry["td_enabled"] == true )
954 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "Teardrop enable defaults" ),
955 _( "The target cannot retain enabled teardrop defaults." ), 1 );
956
957 entry.erase( "td_enabled" );
958 }
959 }
960
961 if( settings.contains( "meta" ) && settings["meta"].is_object() )
962 settings["meta"]["version"] = 2;
963}
964} // namespace
965
966
967void DowngradeProjectFileJson( nlohmann::json& aDoc, const DOWNGRADE_TARGET& aTarget, COMPATIBILITY_REPORT& aReport )
968{
969 auto knownSchema = [&]( const nlohmann::json& aSettings, int aMaximum, const wxString& aName )
970 {
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 )
974 {
975 aReport.Add( DOWNGRADE_BUCKET::BLOCK, _( "Newer project settings schema" ),
976 wxString::Format( _( "The %s schema is newer than the exporter has reviewed." ), aName ) );
977 return false;
978 }
979
980 return true;
981 };
982
983 bool known = knownSchema( aDoc, 4, _( "project" ) );
984
985 if( aDoc.contains( "net_settings" ) )
986 known &= knownSchema( aDoc["net_settings"], 5, _( "net settings" ) );
987
988 if( aDoc.contains( "tuning_profiles" ) )
989 known &= knownSchema( aDoc["tuning_profiles"], 2, _( "tuning profiles" ) );
990
991 if( aDoc.contains( "component_class_settings" ) )
992 known &= knownSchema( aDoc["component_class_settings"], 0, _( "component class settings" ) );
993
994 if( aDoc.contains( "erc" ) )
995 known &= knownSchema( aDoc["erc"], 1, _( "ERC settings" ) );
996
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" ) );
999
1000 if( !known )
1001 return;
1002
1003 // KiCad 10 introduced board format 20260206. Earlier targets also lose its settings.
1004 bool preTen = aTarget.m_boardVersion < 20260206;
1005 bool tuningLoss = false;
1006
1007 downgradeBoardSettings( aDoc, preTen, aReport );
1008 downgradeErcSettings( aDoc, preTen, aReport );
1009 downgradeOutputSettings( aDoc, aReport );
1010
1011 if( aDoc.contains( "net_settings" ) )
1012 {
1013 nlohmann::json& net = aDoc["net_settings"];
1014
1015 if( net.contains( "net_chain_netclasses" ) )
1016 {
1017 if( !net["net_chain_netclasses"].empty() && !net["net_chain_netclasses"].is_null() )
1018 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "Net chain netclass assignments" ),
1019 _( "Removed from the project file." ), 1 );
1020
1021 net.erase( "net_chain_netclasses" );
1022 }
1023
1024 if( net.contains( "net_chain_classes" ) )
1025 {
1026 bool empty = net["net_chain_classes"].is_null() || net["net_chain_classes"].empty();
1027
1028 net.erase( "net_chain_classes" );
1029
1030 if( !empty )
1031 {
1032 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "Net chain classes" ), _( "Removed from the project file." ),
1033 1 );
1034 }
1035 }
1036
1037 if( preTen )
1038 {
1039 if( net.contains( "classes" ) && net["classes"].is_array() )
1040 {
1041 for( nlohmann::json& netclass : net["classes"] )
1042 {
1043 if( netclass.is_object() )
1044 {
1045 if( netclass.contains( "tuning_profile" ) && netclass["tuning_profile"].is_string()
1046 && !netclass["tuning_profile"].get_ref<const std::string&>().empty() )
1047 {
1048 tuningLoss = true;
1049 }
1050
1051 netclass.erase( "tuning_profile" );
1052 }
1053 }
1054 }
1055
1056 if( net.contains( "meta" ) && net["meta"].is_object() )
1057 net["meta"]["version"] = 4;
1058 }
1059 }
1060
1061 if( aDoc.contains( "tuning_profiles" ) )
1062 {
1063 if( preTen )
1064 {
1065 const nlohmann::json defaults = {
1066 { "tuning_profiles_impedance_geometric", nlohmann::json::array() },
1067 };
1068 tuningLoss |= hasConfiguredProjectSettings( aDoc["tuning_profiles"], defaults );
1069 aDoc.erase( "tuning_profiles" );
1070 }
1071 else
1072 {
1073 nlohmann::json& profiles = aDoc["tuning_profiles"];
1074
1075 if( profiles.contains( "tuning_profiles_impedance_geometric" )
1076 && profiles["tuning_profiles_impedance_geometric"].is_array() )
1077 {
1078 for( nlohmann::json& entry : profiles["tuning_profiles_impedance_geometric"] )
1079 {
1080 if( !entry.is_object() )
1081 continue;
1082
1083 if( entry.contains( "frequency" ) && entry["frequency"].is_number()
1084 && entry["frequency"].get<double>() != 1e9 )
1085 {
1086 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "Tuning profile frequencies" ),
1087 _( "The target uses its default calculation frequency." ), 1 );
1088 }
1089
1090 if( entry.contains( "model_solder_mask" ) && entry["model_solder_mask"].is_boolean()
1091 && entry["model_solder_mask"].get<bool>() )
1092 {
1093 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "Tuning profile solder mask modelling" ),
1094 _( "The target cannot include solder mask in the calculation." ), 1 );
1095 }
1096
1097 entry.erase( "frequency" );
1098 entry.erase( "model_solder_mask" );
1099
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 )
1103 {
1104 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "Net chain bridge propagation delay" ),
1105 _( "The target cannot model delay across net chain bridges." ), 1 );
1106 }
1107
1108 entry.erase( "net_chain_bridge_prop_delay" );
1109 }
1110 }
1111
1112 if( profiles.contains( "meta" ) && profiles["meta"].is_object() )
1113 profiles["meta"]["version"] = 0;
1114 }
1115 }
1116
1117 if( tuningLoss )
1118 {
1119 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "Time-domain tuning profiles" ),
1120 _( "Removed saved profiles or netclass profile selections from the project file." ), 1 );
1121 }
1122
1123 if( preTen )
1124 {
1125 // Layer preset render layers are names. Older readers skip names they do not know.
1126 if( aDoc.contains( "component_class_settings" ) )
1127 {
1128 const nlohmann::json defaults = {
1129 { "assignments", nlohmann::json::array() },
1130 { "sheet_component_classes", { { "enabled", false } } },
1131 };
1132 bool configured = hasConfiguredProjectSettings( aDoc["component_class_settings"], defaults );
1133 aDoc.erase( "component_class_settings" );
1134
1135 if( configured )
1136 {
1137 aReport.Add( DOWNGRADE_BUCKET::DROP, _( "Component class assignment rules" ),
1138 _( "Removed from the project file." ), 1 );
1139 }
1140 }
1141
1142 if( aDoc.contains( "schematic" ) && aDoc["schematic"].is_object() )
1143 {
1144 // More than one top-level sheet blocks the schematic export before this runs.
1145 aDoc["schematic"].erase( "top_level_sheets" );
1146
1147 // The 9.0 schematic writer carries the aliases inside every sheet instead.
1148 aDoc["schematic"].erase( "bus_aliases" );
1149
1150 // The schematic export drops the variant registry, so the descriptions go too.
1151 aDoc["schematic"].erase( "variants" );
1152 }
1153 }
1154
1155 // Both released targets use project schema 3. Rewind after converting nested settings.
1156 if( aDoc.contains( "meta" ) && aDoc["meta"].is_object() )
1157 aDoc["meta"]["version"] = 3;
1158}
int index
const char * name
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)
Definition common.cpp:60
@ INTERNAL
Definition common.h:94
@ BLOCK
Cannot be represented at all, export to this target is not possible.
@ DROP
No equivalent, left out.
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
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.
std::string path
VECTOR2I end
wxString dump(const wxArrayString &aArray)
Debug helper for printing wxArrayString contents.