KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_project_file.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
24
29#include <wx/filename.h>
30
31#include <json_common.h>
32
33#include <filesystem>
34#include <fstream>
35#include <sstream>
36
37namespace fs = std::filesystem;
38
39
41{
42public:
44 {
45 m_originalDir = fs::current_path();
46 m_tempDir = fs::temp_directory_path() / "kicad_project_file_test";
47 fs::remove_all( m_tempDir );
48 fs::create_directories( m_tempDir );
49 }
50
52 {
53 fs::current_path( m_originalDir );
54 fs::remove_all( m_tempDir );
55 }
56
57 fs::path m_originalDir;
58 fs::path m_tempDir;
59};
60
61
62BOOST_FIXTURE_TEST_SUITE( ProjectFile, PROJECT_FILE_TEST_FIXTURE )
63
64
65BOOST_AUTO_TEST_CASE( Ipc2581ExplicitEmptySectionsRoundTrips )
66{
67 fs::path projectPath = m_tempDir / "api_kitchen_sink.kicad_pro";
68 fs::copy_file( fs::path( KI_TEST::GetTestDataRootDir() ) / "pcbnew" / "api_kitchen_sink.kicad_pro",
69 projectPath );
70
71 {
73 BOOST_REQUIRE( mgr.LoadProject( wxString( projectPath.string() ), true ) );
75 BOOST_REQUIRE( !project.m_IP2581Bom.sections );
76 project.m_IP2581Bom.sections = wxString();
77 BOOST_REQUIRE( project.SaveToFile( wxString( m_tempDir.string() ), true ) );
78 mgr.UnloadProject( &mgr.Prj(), false );
79 }
80
82 BOOST_REQUIRE( mgr.LoadProject( wxString( projectPath.string() ), true ) );
83 PROJECT_FILE& loaded = mgr.Prj().GetProjectFile();
85 BOOST_CHECK( loaded.m_IP2581Bom.sections->IsEmpty() );
86}
87
88
94BOOST_AUTO_TEST_CASE( SaveAsUpdatesTopLevelSheetNames )
95{
96 fs::path oldProjectDir = m_tempDir / "old_project";
97 fs::path newProjectDir = m_tempDir / "new_project";
98 fs::create_directories( oldProjectDir );
99 fs::create_directories( newProjectDir );
100
101 wxString oldProjectPath = wxString( oldProjectDir.string() ) + wxFileName::GetPathSeparator()
102 + wxS( "old_project." ) + FILEEXT::ProjectFileExtension;
103
104 PROJECT_FILE projectFile( oldProjectPath );
105
106 // Add a top-level sheet with name matching the project name
107 TOP_LEVEL_SHEET_INFO sheetInfo;
108 sheetInfo.uuid = KIID();
109 sheetInfo.name = wxS( "old_project" );
110 sheetInfo.filename = wxS( "old_project.kicad_sch" );
111
112 projectFile.GetTopLevelSheets().push_back( sheetInfo );
113
114 // Add another sheet with a custom name that should NOT be changed
115 TOP_LEVEL_SHEET_INFO customSheet;
116 customSheet.uuid = KIID();
117 customSheet.name = wxS( "CustomSheet" );
118 customSheet.filename = wxS( "custom_sheet.kicad_sch" );
119
120 projectFile.GetTopLevelSheets().push_back( customSheet );
121
122 // Perform SaveAs to new project
123 projectFile.SaveAs( wxString( newProjectDir.string() ), wxS( "new_project" ) );
124
125 // Verify the sheet name was updated
126 const std::vector<TOP_LEVEL_SHEET_INFO>& sheets = projectFile.GetTopLevelSheets();
127
128 BOOST_REQUIRE_EQUAL( sheets.size(), 2 );
129
130 // First sheet's name should be updated to match the new project name
131 BOOST_CHECK_EQUAL( sheets[0].name, wxS( "new_project" ) );
132
133 // First sheet's filename should also be updated
134 BOOST_CHECK_EQUAL( sheets[0].filename, wxS( "new_project.kicad_sch" ) );
135
136 // Second sheet's custom name should remain unchanged
137 BOOST_CHECK_EQUAL( sheets[1].name, wxS( "CustomSheet" ) );
138
139 // Second sheet's filename should remain unchanged
140 BOOST_CHECK_EQUAL( sheets[1].filename, wxS( "custom_sheet.kicad_sch" ) );
141}
142
143
153BOOST_AUTO_TEST_CASE( LoadFixesStaleTopLevelSheetReferences )
154{
155 fs::path projectDir = m_tempDir / "my_project";
156 fs::create_directories( projectDir );
157
158 // Write a .kicad_pro that references "default.kicad_sch" (as if copied from a template)
159 std::string proContent = R"({
160 "meta": {
161 "filename": "my_project.kicad_pro",
162 "version": 3
163 },
164 "schematic": {
165 "top_level_sheets": [
166 {
167 "uuid": "00000000-0000-0000-0000-000000000000",
168 "name": "default",
169 "filename": "default.kicad_sch"
170 }
171 ]
172 }
173 })";
174
175 fs::path proPath = projectDir / "my_project.kicad_pro";
176 std::ofstream proFile( proPath );
177 proFile << proContent;
178 proFile.close();
179
180 // Create the renamed schematic file (as if the template copy renamed it)
181 fs::path schPath = projectDir / "my_project.kicad_sch";
182 std::ofstream schFile( schPath );
183 schFile << "(kicad_sch (version 20231120) (generator \"eeschema\") (generator_version \"9.99\")";
184 schFile << " (uuid \"12345678-1234-1234-1234-123456789abc\")";
185 schFile << " (paper \"A4\"))";
186 schFile.close();
187
188 // Load the project using SETTINGS_MANAGER
189 SETTINGS_MANAGER settingsManager;
190 settingsManager.LoadProject( wxString( proPath.string() ) );
191
192 PROJECT& project = settingsManager.Prj();
193 PROJECT_FILE& projectFile = project.GetProjectFile();
194
195 const std::vector<TOP_LEVEL_SHEET_INFO>& sheets = projectFile.GetTopLevelSheets();
196
197 BOOST_REQUIRE_EQUAL( sheets.size(), 1 );
198
199 // The filename should have been corrected from "default.kicad_sch" to "my_project.kicad_sch"
200 BOOST_CHECK_EQUAL( sheets[0].filename, wxS( "my_project.kicad_sch" ) );
201
202 // The name should also be updated
203 BOOST_CHECK_EQUAL( sheets[0].name, wxS( "my_project" ) );
204}
205
206
210BOOST_AUTO_TEST_CASE( LoadPreservesValidTopLevelSheetReferences )
211{
212 fs::path projectDir = m_tempDir / "valid_project";
213 fs::create_directories( projectDir );
214
215 // Write a .kicad_pro with valid references
216 std::string proContent = R"({
217 "meta": {
218 "filename": "valid_project.kicad_pro",
219 "version": 3
220 },
221 "schematic": {
222 "top_level_sheets": [
223 {
224 "uuid": "00000000-0000-0000-0000-000000000000",
225 "name": "valid_project",
226 "filename": "valid_project.kicad_sch"
227 }
228 ]
229 }
230 })";
231
232 fs::path proPath = projectDir / "valid_project.kicad_pro";
233 std::ofstream proFile( proPath );
234 proFile << proContent;
235 proFile.close();
236
237 // Create the schematic file that matches the reference
238 fs::path schPath = projectDir / "valid_project.kicad_sch";
239 std::ofstream schFile( schPath );
240 schFile << "(kicad_sch (version 20231120) (generator \"eeschema\") (generator_version \"9.99\")";
241 schFile << " (uuid \"12345678-1234-1234-1234-123456789abc\")";
242 schFile << " (paper \"A4\"))";
243 schFile.close();
244
245 SETTINGS_MANAGER settingsManager;
246 settingsManager.LoadProject( wxString( proPath.string() ) );
247
248 PROJECT& project = settingsManager.Prj();
249 PROJECT_FILE& projectFile = project.GetProjectFile();
250
251 const std::vector<TOP_LEVEL_SHEET_INFO>& sheets = projectFile.GetTopLevelSheets();
252
253 BOOST_REQUIRE_EQUAL( sheets.size(), 1 );
254
255 // References should be unchanged
256 BOOST_CHECK_EQUAL( sheets[0].filename, wxS( "valid_project.kicad_sch" ) );
257 BOOST_CHECK_EQUAL( sheets[0].name, wxS( "valid_project" ) );
258}
259
260
274BOOST_AUTO_TEST_CASE( LoadProjectByAbsolutePathIsStable )
275{
276 fs::path projectDir = m_tempDir / "jobset_project";
277 fs::create_directories( projectDir );
278
279 std::string proContent = R"({
280 "meta": {
281 "filename": "jobset_project.kicad_pro",
282 "version": 3
283 }
284 })";
285
286 fs::path proPath = projectDir / "jobset_project.kicad_pro";
287 std::ofstream proFile( proPath );
288 proFile << proContent;
289 proFile.close();
290
291 wxFileName absFn( wxString( proPath.string() ) );
292 absFn.MakeAbsolute();
293 wxString absPath = absFn.GetFullPath();
294
295 SETTINGS_MANAGER settingsManager;
296
297 // Load as the fixed jobset runner does: by absolute path.
298 BOOST_REQUIRE( settingsManager.LoadProject( absPath ) );
299
300 PROJECT* heldProject = settingsManager.GetProject( absPath );
301 BOOST_REQUIRE( heldProject != nullptr );
302
303 // Simulate the kiface board loader resolving and (re)loading the project by its absolute path.
304 BOOST_REQUIRE( settingsManager.LoadProject( absPath, true ) );
305
306 // The second load must be a no-op for the held pointer; the project must not have been evicted.
307 PROJECT* afterReload = settingsManager.GetProject( absPath );
308 BOOST_CHECK( afterReload == heldProject );
309
310 // The held pointer must still resolve to the same project name (i.e. it was not freed).
311 BOOST_CHECK_EQUAL( heldProject->GetProjectFullName(), absPath );
312}
313
314
324BOOST_AUTO_TEST_CASE( UnloadProjectSavesToOwnDirectory )
325{
326 fs::path projADir = m_tempDir / "proj_a";
327 fs::path projBDir = m_tempDir / "proj_b";
328 fs::create_directories( projADir );
329 fs::create_directories( projBDir );
330
331 // Shared basename in different directories reproduces the cross-project clobber. The
332 // matching schematic keeps LoadFromFile from flagging the project as migrated, which
333 // would otherwise suppress the auto-save under test.
334 const std::string projectName = "shared_name";
335
336 auto writeProject = [&]( const fs::path& aDir )
337 {
338 std::string content = "{\n"
339 " \"meta\": {\n"
340 " \"filename\": \"" + projectName + ".kicad_pro\",\n"
341 " \"version\": 3\n"
342 " },\n"
343 " \"schematic\": {\n"
344 " \"top_level_sheets\": [\n"
345 " {\n"
346 " \"uuid\": \"00000000-0000-0000-0000-000000000000\",\n"
347 " \"name\": \"" + projectName + "\",\n"
348 " \"filename\": \"" + projectName + ".kicad_sch\"\n"
349 " }\n"
350 " ]\n"
351 " }\n"
352 "}\n";
353 std::ofstream out( aDir / ( projectName + ".kicad_pro" ) );
354 out << content;
355 out.close();
356
357 std::ofstream sch( aDir / ( projectName + ".kicad_sch" ) );
358 sch << "(kicad_sch (version 20231120) (generator \"eeschema\") (generator_version \"9.99\")";
359 sch << " (uuid \"12345678-1234-1234-1234-123456789abc\") (paper \"A4\"))";
360 sch.close();
361 };
362
363 fs::path proAPath = projADir / ( projectName + ".kicad_pro" );
364 fs::path proBPath = projBDir / ( projectName + ".kicad_pro" );
365 writeProject( projADir );
366 writeProject( projBDir );
367
369
370 // A becomes the active project; B is loaded but left non-active so both are resident.
371 BOOST_REQUIRE( mgr.LoadProject( wxString( proAPath.string() ), true ) );
372 BOOST_REQUIRE( mgr.LoadProject( wxString( proBPath.string() ), false ) );
373
374 PROJECT* projB = mgr.GetProject( wxString( proBPath.string() ) );
375 BOOST_REQUIRE( projB != nullptr );
376
377 // Prj() must be A so that a Prj()-based path resolution would target the wrong directory.
378 BOOST_REQUIRE_EQUAL( mgr.Prj().GetProjectFullName(), wxString( proAPath.string() ) );
379
380 // Mark B's project file so the save has something distinctive to persist.
381 projB->GetProjectFile().m_TextVars[wxS( "OWNER" )] = wxS( "proj_b" );
382
383 BOOST_REQUIRE( mgr.UnloadProject( projB, true ) );
384
385 auto readFile = []( const fs::path& aPath )
386 {
387 std::ifstream in( aPath );
388 std::stringstream buffer;
389 buffer << in.rdbuf();
390 return buffer.str();
391 };
392
393 // B's own file must have received B's marker.
394 std::string savedB = readFile( proBPath );
395 BOOST_CHECK_MESSAGE( savedB.find( "OWNER" ) != std::string::npos,
396 "unloaded project must be saved to its own directory" );
397
398 // A's identically named file must not have been clobbered with B's data.
399 std::string savedA = readFile( proAPath );
400 BOOST_CHECK_MESSAGE( savedA.find( "OWNER" ) == std::string::npos,
401 "active project's file must not receive the unloaded project's data" );
402}
403
404
405// Switching projects must replace the active project even when a passive one sorts first
406BOOST_AUTO_TEST_CASE( LoadActiveProjectReplacesActiveNotPassive )
407{
408 auto projectPath = [&]( const std::string& aName )
409 {
410 fs::path dir = m_tempDir / aName;
411 fs::create_directories( dir );
412 return wxString( ( dir / ( aName + ".kicad_pro" ) ).string() );
413 };
414
415 const wxString active = projectPath( "z_active" );
416 const wxString passive = projectPath( "a_passive" );
417 const wxString next = projectPath( "m_next" );
418
420
421 mgr.LoadProject( active, true );
422 mgr.LoadProject( passive, false );
423 mgr.LoadProject( next, true );
424
426 BOOST_CHECK( mgr.GetProject( active ) == nullptr );
427 BOOST_CHECK( mgr.GetProject( passive ) != nullptr );
428}
429
430
441BOOST_AUTO_TEST_CASE( NoRewriteWhenUnchanged )
442{
443 fs::path projectDir = m_tempDir / "unchanged_project";
444 fs::create_directories( projectDir );
445
446 fs::path proPath = projectDir / "unchanged_project.kicad_pro";
447
448 // Produce a canonical, fully-populated current-version file with KiCad's own writer so the
449 // reload round-trip is otherwise clean.
450 {
451 std::ofstream seed( proPath );
452 seed << R"({"meta":{"version":3}})";
453 seed.close();
454
456 BOOST_REQUIRE( mgr.LoadProject( wxString( proPath.string() ), true ) );
457 BOOST_REQUIRE( mgr.SaveProject() );
458 mgr.UnloadProject( &mgr.Prj(), false );
459 }
460
461 auto readFile = []( const fs::path& aPath )
462 {
463 std::ifstream in( aPath );
464 std::stringstream buffer;
465 buffer << in.rdbuf();
466 return buffer.str();
467 };
468
469 // Files saved before ipc2581 (PARAM) and bus_aliases (PARAM_LAMBDA) existed omit both keys, and a
470 // no-op load must not resurrect their defaults
471 {
472 nlohmann::json js = nlohmann::json::parse( readFile( proPath ) );
473 js["board"].erase( "ipc2581" );
474 js["schematic"].erase( "bus_aliases" );
475
476 std::ofstream out( proPath );
477 out << std::setw( 2 ) << js << std::endl;
478 out.close();
479 }
480
481 std::string before = readFile( proPath );
482 BOOST_REQUIRE( before.find( "ipc2581" ) == std::string::npos );
483 BOOST_REQUIRE( before.find( "bus_aliases" ) == std::string::npos );
484
486 BOOST_REQUIRE( mgr.LoadProject( wxString( proPath.string() ), true ) );
487
488 PROJECT_FILE& projectFile = mgr.Prj().GetProjectFile();
489
490 // The auto-save path must decline to write when nothing changed.
491 BOOST_CHECK( !projectFile.SaveToFile( wxString( projectDir.string() ) ) );
492
493 // And the on-disk file must be byte-for-byte unchanged.
494 BOOST_CHECK_EQUAL( before, readFile( proPath ) );
495
496 // An explicitly cleared alias table must survive reopening even when the old file omitted it
497 mgr.UnloadProject( &mgr.Prj(), false );
498 BOOST_REQUIRE( mgr.LoadProject( wxString( proPath.string() ), true ) );
499 PROJECT_FILE& edited = mgr.Prj().GetProjectFile();
500 BOOST_REQUIRE( !edited.GetJson( "schematic.bus_aliases" ) );
501 edited.m_BusAliasesDefined = true;
502 BOOST_REQUIRE( edited.SaveToFile( wxString( projectDir.string() ) ) );
503 nlohmann::json saved = nlohmann::json::parse( readFile( proPath ) );
504 BOOST_CHECK( saved["schematic"]["bus_aliases"].is_object() );
505 BOOST_CHECK( saved["schematic"]["bus_aliases"].empty() );
506}
507
508
509BOOST_AUTO_TEST_CASE( AliasMigrationPreservesNonemptyDefinitions )
510{
511 const fs::path projectPath = m_tempDir / "aliases.kicad_pro";
512 const nlohmann::json aliases = { { "USB", { "D+", "D-" } } };
513
514 {
515 std::ofstream output( projectPath );
516 output << nlohmann::json( { { "meta", { { "version", 3 } } },
517 { "schematic", { { "bus_aliases", aliases } } } } ).dump( 2 );
518 output.close();
519 BOOST_REQUIRE( output.good() );
520 }
521
522 SETTINGS_MANAGER manager;
523 BOOST_REQUIRE( manager.LoadProject( wxString( projectPath.string() ), true ) );
524 PROJECT_FILE& project = manager.Prj().GetProjectFile();
525 BOOST_REQUIRE( project.m_BusAliasesDefined );
526 BOOST_REQUIRE_EQUAL( project.m_BusAliases.size(), 1 );
527 BOOST_CHECK( project.m_BusAliases.at( "USB" ) == std::vector<wxString>( { "D+", "D-" } ) );
528 BOOST_REQUIRE( project.SaveToFile( wxString( m_tempDir.string() ) ) );
529 std::ifstream input( projectPath );
530 const nlohmann::json saved = nlohmann::json::parse( input );
531 BOOST_CHECK_EQUAL( saved["meta"]["version"].get<int>(), 4 );
532 BOOST_CHECK( saved["schematic"]["bus_aliases"] == aliases );
533}
534
535
536BOOST_AUTO_TEST_CASE( LegacyFieldNameTemplatesSurviveMigration )
537{
538 const fs::path legacyPath = m_tempDir / "field_names.pro";
539 const fs::path projectPath = m_tempDir / "field_names.kicad_pro";
540
541 {
542 std::ofstream output( legacyPath );
543 output << "[schematic_editor]\n"
544 "FieldNameTemplates=(templatefields (field (name \"Manufacturer\") visible url))\n";
545 }
546
547 PROJECT_FILE project( wxString( projectPath.string() ) );
548 BOOST_REQUIRE( project.LoadFromFile() );
549
550 const auto& fields = project.m_TemplateFieldNames.GetTemplateFieldNames( TEMPLATES::SCOPE::PROJECT );
551 BOOST_REQUIRE_EQUAL( fields.size(), 1 );
552 BOOST_CHECK_EQUAL( fields[0].m_Name, wxS( "Manufacturer" ) );
553 BOOST_CHECK( fields[0].m_Visible );
554 BOOST_CHECK( fields[0].m_URL );
555
556 const nlohmann::json saved = nlohmann::json::parse( project.FormatAsString() );
557 const auto& fieldNames = saved["schematic"]["drawing"]["field_names"];
558 BOOST_REQUIRE( fieldNames.is_array() );
559 BOOST_REQUIRE_EQUAL( fieldNames.size(), 1 );
560 BOOST_CHECK_EQUAL( fieldNames[0]["name"].get<std::string>(), "Manufacturer" );
561}
562
563
564BOOST_AUTO_TEST_CASE( EmptyFieldNameArrayClearsTemplatesOnReload )
565{
566 const fs::path projectPath = m_tempDir / "field_names.kicad_pro";
567 nlohmann::json projectJson = { { "meta", { { "version", 4 } } } };
568 projectJson["schematic"]["drawing"]["field_names"] =
569 nlohmann::json::array( { { { "name", "Manufacturer" }, { "visible", true }, { "url", false } } } );
570
571 auto writeProject = [&]()
572 {
573 std::ofstream output( projectPath );
574 output << projectJson.dump( 2 );
575 };
576
577 writeProject();
578
579 PROJECT_FILE project( wxString( projectPath.string() ) );
580 BOOST_REQUIRE( project.LoadFromFile() );
581 BOOST_REQUIRE_EQUAL( project.m_TemplateFieldNames.GetTemplateFieldNames( TEMPLATES::SCOPE::PROJECT ).size(), 1 );
582
583 projectJson["schematic"]["drawing"]["field_names"] = nlohmann::json::array();
584 writeProject();
585
586 BOOST_REQUIRE( project.LoadFromFile() );
587 BOOST_CHECK( project.m_TemplateFieldNames.GetTemplateFieldNames( TEMPLATES::SCOPE::PROJECT ).empty() );
588}
589
590
const char * name
std::optional< nlohmann::json > GetJson(const std::string &aPath) const
Fetches a JSON object that is a subset of this JSON_SETTINGS object, using a path of the form "key1....
Definition kiid.h:46
The backing store for a PROJECT, in JSON format.
std::map< wxString, wxString > m_TextVars
bool m_BusAliasesDefined
An explicit alias table was loaded or set, including a deliberately empty table.
struct IP2581_BOM m_IP2581Bom
Layer pair list for the board.
bool SaveAs(const wxString &aDirectory, const wxString &aFile)
std::vector< TOP_LEVEL_SHEET_INFO > & GetTopLevelSheets()
bool SaveToFile(const wxString &aDirectory="", bool aForce=false) override
Calls Store() and then writes the contents of the JSON document to a file.
Container for project specific data.
Definition project.h:63
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
Definition project.cpp:270
virtual PROJECT_FILE & GetProjectFile() const
Definition project.h:201
bool SaveProject(const wxString &aFullPath=wxEmptyString, PROJECT *aProject=nullptr)
Save a loaded project.
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT * GetProject(const wxString &aFullPath) const
Retrieve a loaded project by name.
bool UnloadProject(PROJECT *aProject, bool aSave=true)
Save, unload and unregister the given PROJECT.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
static bool empty(const wxTextEntryBase *aCtrl)
static const std::string ProjectFileExtension
std::string GetTestDataRootDir()
static bool readFile(const wxString &aFileName, wxString &aOut, size_t aLimit=0)
Read a file into aOut.
CITER next(CITER it)
Definition ptree.cpp:120
std::optional< wxString > sections
Table 4 section key, unset when the mode picks the sections.
Information about a top-level schematic sheet.
KIID uuid
Unique identifier for the sheet.
wxString name
Display name for the sheet.
wxString filename
Relative path to the sheet file.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(Ipc2581ExplicitEmptySectionsRoundTrips)
BOOST_CHECK_EQUAL(result, "25.4")
wxString dump(const wxArrayString &aArray)
Debug helper for printing wxArrayString contents.
Definition of file extensions used in Kicad.