From e19c788f3010b533ec2aee8ab4df63170eb352f6 Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Mon, 26 Oct 2026 20:23:26 +0100 Subject: [PATCH] Catenary as plain node entries in scenery files --- application/editormode.cpp | 32 ++++- application/editormodecatenary.cpp | 68 +++++---- application/editormodecatenary.h | 2 + lang/editor_pl.po | 20 +++ scene/scenelayers.h | 11 +- scene/scenelayerssave.cpp | 105 +++++++++++++- simulation/simulationstateserializer.cpp | 6 + world/catenary_scene.cpp | 172 ++++++++++++++++++----- world/catenary_scene.h | 11 ++ 9 files changed, 356 insertions(+), 71 deletions(-) diff --git a/application/editormode.cpp b/application/editormode.cpp index bdd80380..ff4daced 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -3004,15 +3004,30 @@ bool editor_mode::save() std::transform(b.begin(), b.end(), b.begin(), [](unsigned char const C) { return static_cast(std::tolower(C)); }); return a == b; }; + // the catenary straight in the scenery files: the node entries of the changed ones go into the files of their layers anew + scene::node_blocks catenaryblocks; + catenaryblocks.kinds = {"suspension", "chain", "gantry", "anchor"}; + std::vector catenaryinline; for (auto const &file : catenary_scene_files()) { - // a catenary file of the name of a scenery file would take its place, and the scenery its directive - for (std::size_t layer = 1; layer <= scene::Layers.size(); ++layer) + if (false == catenary_scene_file_inline(file) || false == catenary_scene_file_changed(file)) + continue; + scene::layer_handle target{null_handle}; + for (std::size_t layer = 1; layer <= scene::Layers.size() && target == null_handle; ++layer) if (scene::Layers.listed(static_cast(layer)) && same_file(scene::Layers.layer(static_cast(layer)).name, file)) - { - ui()->set_status(format(STR_C("The catenary couldn't be saved: its file \"%s\" is a file of the scenery; give the catenary another file"), file.c_str()), true); - return false; - } + target = static_cast(layer); + std::string reason; + if (target == null_handle) + reason = STR("it isn't in a scenery file the editor can rewrite (an include template)"); + else + scene::Layers.accepts(target, &reason); + if (target == null_handle || false == scene::Layers.accepts(target)) + { + ui()->set_status(format(STR_C("The catenary in \"%s\" couldn't be saved: %s"), file.c_str(), reason.c_str()), true); + return false; + } + catenaryblocks.lines[target] = catenary_scene_entries(file); + catenaryinline.push_back(file); } if (catenary_scene_changed()) { @@ -3055,7 +3070,10 @@ bool editor_mode::save() trailing.insert(trailing.end(), trainset.lines.begin(), trainset.lines.end()); if (placed == 0) starter_trainset(trailing); - auto const result = scene::Layers.save(rootstatements, trailing, layerstatements); + auto const result = scene::Layers.save(rootstatements, trailing, layerstatements, &catenaryblocks); + if (result.success) + for (auto const &file : catenaryinline) + catenary_scene_file_saved(file); if (result.success && controls) m_turntable_included = true; if (result.success && noses) diff --git a/application/editormodecatenary.cpp b/application/editormodecatenary.cpp index 2c21fe14..88530b78 100644 --- a/application/editormodecatenary.cpp +++ b/application/editormodecatenary.cpp @@ -695,29 +695,34 @@ void catenary_editor::catenary_file_follow() auto const active{scene::Layers.active()}; if (false == scene::Layers.valid(active)) return; // the scenery wasn't opened for editing: no layers - // the catenary file of the active layer (the one it includes), or a new one named after it next to it; not while a chain - // goes on (a chain is in one file) nor when the user gave the file + // the new poles go into the file of the active layer, as its node entries; not while a chain goes on (a chain is in one + // file) nor when the user gave the file if (false == tool.file_own && tool.last < 0 && tool.selected < 0) - { - std::string wanted; - for (auto const &file : catenary_scene_files()) - if (catenary_scene_file_layer(file) == active) - { - wanted = file; - break; - } - if (wanted.empty()) - { - std::filesystem::path const name{scene::Layers.layer(active).name}; - wanted = (name.parent_path() / (name.stem().string() + "_siec.scm")).generic_string(); - } - std::snprintf(tool.file, sizeof(tool.file), "%s", wanted.c_str()); - } - // a file the scenery doesn't include yet gets its directive in the file of the active layer - if (tool.file[0] != '\0') + std::snprintf(tool.file, sizeof(tool.file), "%s", scene::Layers.layer(active).name.c_str()); + if (tool.file[0] == '\0') + return; + // the file of a layer takes the entries straight; another one is a catenary file of its own (catenary endcatenary), + // whose directive goes into the file of the active layer + if (catenary_layer_of(tool.file) != 0) + catenary_scene_file_inline(tool.file, true); + else if (false == catenary_scene_file_inline(tool.file)) catenary_scene_file_layer(tool.file, active); } +std::size_t catenary_editor::catenary_layer_of(std::string const &File) +{ + auto const same = [](std::string const &A, std::string const &B) { + auto a{std::filesystem::path(A).lexically_normal().generic_string()}, b{std::filesystem::path(B).lexically_normal().generic_string()}; + std::transform(a.begin(), a.end(), a.begin(), [](unsigned char const C) { return static_cast(std::tolower(C)); }); + std::transform(b.begin(), b.end(), b.begin(), [](unsigned char const C) { return static_cast(std::tolower(C)); }); + return a == b; + }; + for (std::size_t layer = 1; layer <= scene::Layers.size(); ++layer) + if (scene::Layers.listed(static_cast(layer)) && same(scene::Layers.layer(static_cast(layer)).name, File)) + return layer; + return 0; +} + bool catenary_editor::typed_span() const { auto const &tool{m_catenary}; @@ -4278,7 +4283,7 @@ void catenary_editor::render_catenary_cantilever_ui() if (ImGui::InputText(STR_C("Catenary file"), tool.file, sizeof(tool.file))) tool.file_own = true; if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", STR_C("File in the scenery directory the new poles go to; a new one is added to the scenery with the directive catenary endcatenary when it's saved, in the file of the active layer. By default the catenary file of the active layer, or a new one named after it")); + ImGui::SetTooltip("%s", STR_C("Where the new poles go: by default the file of the active layer, as its node entries (node ... suspension / chain / gantry / anchor), saved with the scenery. Another file name makes a catenary file of its own, included with catenary endcatenary from the file of the active layer")); if (tool.file_own) { ImGui::SameLine(); @@ -4288,15 +4293,20 @@ void catenary_editor::render_catenary_cantilever_ui() ImGui::SetTooltip("%s", STR_C("The catenary file follows the active layer again")); } { - // where it ends up: the file, and the layer whose file includes it - auto const layer{static_cast(catenary_scene_file_layer(tool.file))}; - auto const files{catenary_scene_files()}; - auto const known{std::find(files.begin(), files.end(), std::string{tool.file}) != files.end()}; - auto const fresh{catenary_scene_new_files()}; - auto const created{known && std::find(fresh.begin(), fresh.end(), std::string{tool.file}) != fresh.end()}; - auto const layername{scene::Layers.valid(layer) ? scene::Layers.layer(layer).name : std::string{}}; - if (false == layername.empty()) - ImGui::TextDisabled("%s", format(known && false == created ? STR_C("included by %s") : STR_C("new: the save writes it and its directive into %s"), layername.c_str()).c_str()); + // where it ends up: straight in the file of a layer, or a catenary file of its own and the layer whose file includes it + if (catenary_scene_file_inline(tool.file)) + ImGui::TextDisabled("%s", STR_C("node entries in this scenery file (layer), saved with the scenery")); + else + { + auto const layer{static_cast(catenary_scene_file_layer(tool.file))}; + auto const files{catenary_scene_files()}; + auto const known{std::find(files.begin(), files.end(), std::string{tool.file}) != files.end()}; + auto const fresh{catenary_scene_new_files()}; + auto const created{known && std::find(fresh.begin(), fresh.end(), std::string{tool.file}) != fresh.end()}; + auto const layername{scene::Layers.valid(layer) ? scene::Layers.layer(layer).name : std::string{}}; + if (false == layername.empty()) + ImGui::TextDisabled("%s", format(known && false == created ? STR_C("catenary file included by %s") : STR_C("catenary file of its own: the save writes it and its directive into %s"), layername.c_str()).c_str()); + } } render_catenary_chain_state(); } diff --git a/application/editormodecatenary.h b/application/editormodecatenary.h index f107f438..cdafc45e 100644 --- a/application/editormodecatenary.h +++ b/application/editormodecatenary.h @@ -170,6 +170,8 @@ struct catenary_editor void catenary_scan(); // the catenary file of the new poles follows the active layer of the scenery void catenary_file_follow(); + // the scenery layer of this file name; 0 none + static std::size_t catenary_layer_of(std::string const &File); // Placing: the spot of a new pole, which takes the zigzag proposed after the last pole and the step of the span bool catenary_spot_at(TTrack &Track, int const Path, glm::dvec3 const &Point, glm::dvec3 const &Cursor, int const Side, catenary_spot &Spot, bool const Placing = false) const; // the zigzag (Iet-107): the largest staggers allowed for the speed of the line, and those the next poles get diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 15bdbe6b..e269e623 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -11747,3 +11747,23 @@ msgstr "Część sieci trakcyjnej: jej kotwienie jest zaznaczone w narzędziu Si msgid "A part of the catenary: its chain is selected in the Catenary tool" msgstr "Część sieci trakcyjnej: jej łańcuch jest zaznaczony w narzędziu Sieć trakcyjna" + +# scenery editor: catenary - straight in the scenery files + +msgid "node entries in this scenery file (layer), saved with the scenery" +msgstr "wpisy node w tym pliku scenerii (warstwie), zapisywane razem ze scenerią" + +msgid "catenary file included by %s" +msgstr "osobny plik sieci, dołączany przez %s" + +msgid "catenary file of its own: the save writes it and its directive into %s" +msgstr "osobny plik sieci: zapis utworzy go i dopisze dyrektywę do %s" + +msgid "Where the new poles go: by default the file of the active layer, as its node entries (node ... suspension / chain / gantry / anchor), saved with the scenery. Another file name makes a catenary file of its own, included with catenary endcatenary from the file of the active layer" +msgstr "Gdzie trafiają nowe słupy: domyślnie do pliku aktywnej warstwy, jako jej wpisy node (node ... suspension / chain / gantry / anchor), zapisywane razem ze scenerią. Inna nazwa pliku tworzy osobny plik sieci, dołączany dyrektywą catenary endcatenary z pliku aktywnej warstwy" + +msgid "it isn't in a scenery file the editor can rewrite (an include template)" +msgstr "nie jest w pliku scenerii, który edytor może przepisać (szablon include)" + +msgid "The catenary in \"%s\" couldn't be saved: %s" +msgstr "Nie udało się zapisać sieci trakcyjnej w \"%s\": %s" diff --git a/scene/scenelayers.h b/scene/scenelayers.h index 32fb533a..4fefe6f7 100644 --- a/scene/scenelayers.h +++ b/scene/scenelayers.h @@ -144,6 +144,14 @@ struct shape_source bool erased{false}; // the definition goes from the file on save }; +// node definitions the editor writes all at once for a layer (the catenary): on save the definitions "node ... ... end" +// of these kinds are taken out of the files of the given layers and the given lines go into them instead, like the new definitions +struct node_blocks +{ + std::set kinds; // node types, lower case + std::map> lines; +}; + struct save_result { bool success{false}; @@ -350,7 +358,8 @@ class node_layers // memory cells, and layer changes. the files are left intact unless the whole operation can be performed. // Rootstatements: additional statements to place at the end of the scenario file // Layerstatements: additional statements to place in the files of specified layers, the same way - save_result save(std::vector const &Rootstatements = {}, std::vector const &Trailingstatements = {}, std::map> const &Layerstatements = {}); + // Blocks: node definitions written anew as a whole, see node_blocks + save_result save(std::vector const &Rootstatements = {}, std::vector const &Trailingstatements = {}, std::map> const &Layerstatements = {}, node_blocks const *Blocks = nullptr); std::string check_sources() const; private: diff --git a/scene/scenelayerssave.cpp b/scene/scenelayerssave.cpp index 72de93e8..e8cc65a1 100644 --- a/scene/scenelayerssave.cpp +++ b/scene/scenelayerssave.cpp @@ -633,7 +633,75 @@ bool node_layers::compose(save_state &State, layer_handle const Layer, compositi } // writes changes made in the editor to the scenery files -save_result node_layers::save(std::vector const &Rootstatements, std::vector const &Trailingstatements, std::map> const &Layerstatements) +namespace +{ + +// locations of the node definitions of specified kinds in the text of a scenery file: from "node" to the closing "end", +// tokens split as the parser does it, comments skipped +std::vector node_definitions(std::string const &Text, std::set const &Kinds) +{ + std::vector result; + std::size_t position{0}; + auto const size{Text.size()}; + auto const separator = [&](std::size_t const At) { return Text[At] == ' ' || Text[At] == '\t' || Text[At] == '\n' || Text[At] == '\r' || Text[At] == ';'; }; + auto const comment = [&](std::size_t const At) { return At + 1 < size && Text[At] == '/' && (Text[At + 1] == '/' || Text[At + 1] == '*'); }; + // the next token: its range, false at the end of the text + auto const next = [&](std::size_t &Begin, std::size_t &End) { + while (position < size) + { + if (separator(position)) + ++position; + else if (comment(position)) + { + auto const block{Text[position + 1] == '*'}; + auto const close{block ? Text.find("*/", position + 2) : Text.find('\n', position + 2)}; + position = close == std::string::npos ? size : close + (block ? 2 : 1); + } + else + break; + } + if (position >= size) + return false; + Begin = position; + while (position < size && false == separator(position) && false == comment(position)) + ++position; + End = position; + return true; + }; + auto const word = [&](std::size_t const Begin, std::size_t const End) { + auto text{Text.substr(Begin, End - Begin)}; + std::transform(text.begin(), text.end(), text.begin(), [](unsigned char const C) { return static_cast(std::tolower(C)); }); + return text; + }; + std::size_t begin{0}, end{0}; + while (next(begin, end)) + { + if (word(begin, end) != "node") + continue; + auto const start{begin}; + // range max, range min, name, type + auto complete{true}; + for (int k = 0; k < 4 && complete; ++k) + complete = next(begin, end); + if (false == complete) + break; + auto const kind{word(begin, end)}; + if (Kinds.count(kind) == 0) + continue; + auto const closing{"end" + kind}; + while (next(begin, end)) + if (word(begin, end) == closing) + { + result.push_back({static_cast(start), static_cast(end)}); + break; + } + } + return result; +} + +} // namespace + +save_result node_layers::save(std::vector const &Rootstatements, std::vector const &Trailingstatements, std::map> const &Layerstatements, node_blocks const *Blocks) { save_result result; if (m_layers.empty()) @@ -1316,6 +1384,30 @@ save_result node_layers::save(std::vector const &Rootstatements, st } } + // node definitions written anew as a whole: the old ones go, the new ones are added to the file like new definitions + std::map> blocktext; + if (Blocks != nullptr) + { + for (auto const &[blocklayer, lines] : Blocks->lines) + { + auto const target{resolve(blocklayer)}; + if (false == is_output(target)) + { + continue; + } + if (false == load(target)) + { + return fail(state.error); + } + for (auto const &span : node_definitions(state.content[target], Blocks->kinds)) + { + push_edit(target, span, {}, nullptr); + } + auto &text{blocktext[target]}; + text.insert(text.end(), lines.begin(), lines.end()); + } + } + // includes of templates std::map> placed; // directives made in the editor, by target layer for (std::size_t idx = 0; idx < m_instances.size(); ++idx) @@ -1455,7 +1547,8 @@ save_result node_layers::save(std::vector const &Rootstatements, st continue; } if (layer(candidate).created || state.patches.count(candidate) != 0 || appended.count(candidate) != 0 || created.count(candidate) != 0 || placed.count(candidate) != 0 || - markedtext.count(candidate) != 0 || (candidate == root && (false == Rootstatements.empty() || false == Trailingstatements.empty())) || Layerstatements.count(candidate) != 0) + markedtext.count(candidate) != 0 || (candidate == root && (false == Rootstatements.empty() || false == Trailingstatements.empty())) || Layerstatements.count(candidate) != 0 || + blocktext.count(candidate) != 0) { outputs.emplace_back(candidate); } @@ -1576,6 +1669,14 @@ save_result node_layers::save(std::vector const &Rootstatements, st added.text += statement + eol; } } + if (auto const lookup{blocktext.find(output)}; lookup != blocktext.end()) + { + for (auto const &line : lookup->second) + { + ensure_newline(added.text, eol); + added.text += line + eol; + } + } if (output == root && false == Trailingstatements.empty()) { composition trailing; diff --git a/simulation/simulationstateserializer.cpp b/simulation/simulationstateserializer.cpp index 5152b2d1..ec801024 100644 --- a/simulation/simulationstateserializer.cpp +++ b/simulation/simulationstateserializer.cpp @@ -583,6 +583,12 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch road->create_lanes(); scene::Layers.track( road, { sourcebegin, Input.TokenEnd() } ); } + else if( nodedata.type == "suspension" || nodedata.type == "chain" || nodedata.type == "gantry" || nodedata.type == "anchor" ) { + // the catenary straight in the scenery file: it belongs to the file (layer) it's in, the scenery save writes it there + auto const layer { scene::Layers.handle() }; + auto const file { ( layer != null_handle && Input.InLayerFile() ) ? scene::Layers.layer( layer ).name : Input.Name() }; + catenary_scene_node( Input, nodedata.type, nodedata.range_max, nodedata.range_min, nodedata.name, file ); + } else if( nodedata.type == "sweep" ) { auto *sweep { new sweep_node( nodedata ) }; diff --git a/world/catenary_scene.cpp b/world/catenary_scene.cpp index 32fb66c1..e24098dd 100644 --- a/world/catenary_scene.cpp +++ b/world/catenary_scene.cpp @@ -78,6 +78,8 @@ struct scene_file { std::vector Files; // warstwa scenerii (plik), która dołącza plik sieci dyrektywą "catenary"; dla plików z edytora ta, do której trafi dyrektywa std::map FileLayers; +// "pliki" sieci, których wpisy są zwykłymi wpisami node w pliku scenerii (warstwie): zapisuje je zapis scenerii, nie catenary_scene_save() +std::set InlineFiles; // łańcuch sieci z pliku: przęsła od podwieszenia do podwieszenia struct scene_chain { @@ -1649,6 +1651,86 @@ umbrella_members( std::string const &Name ) { } // namespace +namespace { + +// wpisy rozebranego pliku (albo jednego wpisu node ze scenerii) do sceny: podwieszenia czekają na catenary_scene_resolve(), +// bramki, kotwienia i łańcuchy są od razu +void +add_content( catenary::suspension_file const &File, std::string const &Filename ) { + for( auto const &entry : File.entries ) { + scene_suspension suspension; + suspension.data = entry; + suspension.skin = ( entry.skin.empty() ? File.skin : entry.skin ); + suspension.file = Filename; + Suspensions.emplace_back( suspension ); + } + for( auto const &line : File.gantries ) { + scene_gantry gantry; + gantry.data = line; + gantry.file = Filename; + Gantries.emplace_back( gantry ); + assemble_gantry( Gantries.back() ); + } + // kotwienia przed łańcuchami: ostatnie przęsło łańcucha kończy się na ich urządzeniu (łańcuch wpisany wcześniej idzie za nim) + for( auto const &line : File.anchors ) { + scene_anchor anchor; + anchor.data = line; + anchor.file = Filename; + Anchors.emplace_back( anchor ); + assemble_anchor( Anchors.back() ); + follow_anchor( Anchors.back() ); + } + // przęsła łańcuchów powstają od razu: przed inicjalizacją scenerii łączy je InitTraction razem z resztą sieci + for( auto const &line : File.chains ) { + scene_chain chain; + chain.data = line; + chain.file = Filename; + Chains.emplace_back( chain ); + rebuild_chain( Chains.back() ); + } +} + +} // namespace + +void +catenary_scene_node( cParser &Input, std::string const &Type, double const RangeMax, double const RangeMin, std::string const &Name, std::string const &File ) { + + // treść wpisu do jego końca, z wielkością liter (symbole typów sieci, ścieżki tekstur) + auto const closing { "end" + Type }; + std::vector tokens { "node", std::to_string( RangeMax ), std::to_string( RangeMin ), ( Name.empty() ? std::string( "none" ) : Name ), Type }; + auto const line { Input.Line() }; + while( true ) { + auto token { Input.getToken( false ) }; + if( token.empty() ) { + ErrorLog( "Bad scenario: " + Type + " \"" + Name + "\" without \"" + closing + "\" in file \"" + Input.Name() + "\" (line " + std::to_string( line ) + ")" ); + return; + } + auto lower { token }; + std::transform( lower.begin(), lower.end(), lower.begin(), []( unsigned char const C ) { return static_cast( std::tolower( C ) ); } ); + tokens.emplace_back( token ); + if( lower == closing ) { tokens.back() = closing; break; } + } + if( false == Global.bLoadTraction ) { return; } + catenary::suspension_file file; + std::vector errors; + catenary::parse_suspensions( tokens, File, file, errors ); + for( auto const &error : errors ) { + ErrorLog( error ); + } + InlineFiles.emplace( File ); + if( file_named( File ) == nullptr ) { + scene_file known; + known.name = File; + Files.emplace_back( known ); + } + FileLayers.emplace( File, static_cast( scene::Layers.handle() ) ); + add_content( file, File ); + if( Initialized ) { + // wpis po FirstInit: tory, przęsła i modele już są + catenary_scene_resolve(); + } +} + // obsługa dyrektywy "catenary" ze scenerii: wczytuje wskazany plik podwieszeń void catenary_scene_load( cParser &Input ) { @@ -1686,36 +1768,7 @@ catenary_scene_load( cParser &Input ) { for( auto const &error : errors ) { ErrorLog( error ); } - for( auto const &entry : file.entries ) { - scene_suspension suspension; - suspension.data = entry; - suspension.skin = ( entry.skin.empty() ? file.skin : entry.skin ); - suspension.file = filename; - Suspensions.emplace_back( suspension ); - } - for( auto const &line : file.gantries ) { - scene_gantry gantry; - gantry.data = line; - gantry.file = filename; - Gantries.emplace_back( gantry ); - assemble_gantry( Gantries.back() ); - } - // kotwienia przed łańcuchami: ostatnie przęsło łańcucha kończy się na ich urządzeniu - for( auto const &line : file.anchors ) { - scene_anchor anchor; - anchor.data = line; - anchor.file = filename; - Anchors.emplace_back( anchor ); - assemble_anchor( Anchors.back() ); - } - // przęsła łańcuchów powstają od razu: przed inicjalizacją scenerii łączy je InitTraction razem z resztą sieci - for( auto const &line : file.chains ) { - scene_chain chain; - chain.data = line; - chain.file = filename; - Chains.emplace_back( chain ); - rebuild_chain( Chains.back() ); - } + add_content( file, filename ); // wczytywany z pliku tej warstwy (tylko przy otwarciu scenerii do edycji, inaczej warstw nie ma) FileLayers.emplace( filename, static_cast( scene::Layers.handle() ) ); if( file_named( filename ) == nullptr ) { @@ -1837,6 +1890,7 @@ catenary_scene_clear() { Anchors.clear(); Files.clear(); FileLayers.clear(); + InlineFiles.clear(); Notes.clear(); Resolved = 0; Initialized = false; @@ -2090,10 +2144,59 @@ catenary_scene_changed() { std::vector catenary_scene_new_files() { std::vector names; - for( auto const &file : Files ) { if( file.created ) { names.push_back( file.name ); } } + for( auto const &file : Files ) { if( file.created && InlineFiles.count( file.name ) == 0 ) { names.push_back( file.name ); } } return names; } +bool +catenary_scene_file_inline( std::string const &File ) { + return InlineFiles.count( File ) != 0; +} + +void +catenary_scene_file_inline( std::string const &File, bool const Inline ) { + if( Inline ) { InlineFiles.emplace( File ); } + else { InlineFiles.erase( File ); } +} + +bool +catenary_scene_file_changed( std::string const &File ) { + auto const *file { file_named( File ) }; + return file != nullptr && file->changed; +} + +void +catenary_scene_file_saved( std::string const &File ) { + if( auto *file { file_named( File ) }; file != nullptr ) { + file->changed = false; + file->created = false; + } +} + +std::vector +catenary_scene_entries( std::string const &File ) { + // w kolejności wczytywania wpisów node: podwieszenia, bramki, kotwienia, łańcuchy + std::vector lines; + auto const *file { file_named( File ) }; + auto const skin { file != nullptr ? file->skin : std::string() }; + for( auto const &suspension : Suspensions ) { + if( suspension.removed || suspension.file != File ) { continue; } + auto data { suspension.data }; + if( data.skin.empty() ) { data.skin = skin; } + lines.emplace_back( catenary::write_suspension( data ) ); + } + for( auto const &gantry : Gantries ) { + if( false == gantry.removed && gantry.file == File ) { lines.emplace_back( catenary::write_gantry( gantry.data ) ); } + } + for( auto const &anchor : Anchors ) { + if( false == anchor.removed && anchor.file == File ) { lines.emplace_back( catenary::write_anchor( anchor.data ) ); } + } + for( auto const &chain : Chains ) { + if( false == chain.removed && chain.file == File ) { lines.emplace_back( catenary::write_chain( chain.data ) ); } + } + return lines; +} + void catenary_scene_file_included( std::string const &File ) { if( auto *file { file_named( File ) }; file != nullptr ) { file->created = false; } @@ -2138,7 +2241,8 @@ catenary_scene_save( std::string &Message ) { std::vector written; for( auto &file : Files ) { - if( false == file.changed ) { continue; } + // wpisy w pliku scenerii zapisuje zapis scenerii + if( false == file.changed || InlineFiles.count( file.name ) != 0 ) { continue; } // wpisy w kolejności listy; usunięte pomijane. CRLF jak w pozostałych plikach scenerii std::string text; std::istringstream header { file.header }; @@ -2200,6 +2304,8 @@ catenary_scene_new_chain_name( std::string const &File ) { auto stem { File }; if( auto const slash { stem.find_last_of( "/\\" ) }; slash != std::string::npos ) { stem.erase( 0, slash + 1 ); } if( auto const dot { stem.rfind( '.' ) }; dot != std::string::npos ) { stem.erase( dot ); } + // małymi literami: nazwy wpisów node w scenerii parser czyta małymi literami + std::transform( stem.begin(), stem.end(), stem.begin(), []( unsigned char const C ) { return static_cast( std::tolower( C ) ); } ); for( int number = 1; ; ++number ) { auto const name { stem + "_l" + std::to_string( number ) }; auto const taken { std::any_of( Chains.begin(), Chains.end(), [ & ]( scene_chain const &Chain ) { return Chain.data.name == name; } ) }; @@ -2389,6 +2495,8 @@ catenary_scene_new_gantry_name( std::string const &File ) { auto stem { File }; if( auto const slash { stem.find_last_of( "/\\" ) }; slash != std::string::npos ) { stem.erase( 0, slash + 1 ); } if( auto const dot { stem.rfind( '.' ) }; dot != std::string::npos ) { stem.erase( dot ); } + // małymi literami: nazwy wpisów node w scenerii parser czyta małymi literami + std::transform( stem.begin(), stem.end(), stem.begin(), []( unsigned char const C ) { return static_cast( std::tolower( C ) ); } ); for( int number = 1; ; ++number ) { auto const name { stem + "_b" + std::to_string( number ) }; if( std::none_of( Gantries.begin(), Gantries.end(), [ & ]( scene_gantry const &Gantry ) { return Gantry.data.name == name; } ) ) { return name; } diff --git a/world/catenary_scene.h b/world/catenary_scene.h index 94e7875b..795c4e08 100644 --- a/world/catenary_scene.h +++ b/world/catenary_scene.h @@ -18,6 +18,9 @@ class cParser; // obsługa dyrektywy "catenary" ze scenerii: wczytuje wskazany plik podwieszeń void catenary_scene_load( cParser &Input ); +// wpis "node suspension|chain|gantry|anchor ... end" wprost w pliku scenerii; Input stoi za typem. +// File: plik scenerii (warstwa), do którego wpis należy - jego wpisy sieci zapisuje zapis scenerii +void catenary_scene_node( cParser &Input, std::string const &Type, double RangeMax, double RangeMin, std::string const &Name, std::string const &File ); // po wczytaniu scenerii: wiąże podwieszenia z konstrukcjami wsporczymi i przęsłami po nazwach, zgłasza braki void catenary_scene_resolve(); // przed wczytaniem nowej scenerii: usuwa podwieszenia poprzedniej @@ -73,6 +76,14 @@ bool catenary_scene_changed(); std::vector catenary_scene_new_files(); // sceneria dołącza już plik (edytor dopisał dyrektywę "catenary") void catenary_scene_file_included( std::string const &File ); +// wpisy pliku są zwykłymi wpisami node w pliku scenerii o tej nazwie (warstwie): zapisuje je zapis scenerii, bez dyrektywy "catenary" +bool catenary_scene_file_inline( std::string const &File ); +void catenary_scene_file_inline( std::string const &File, bool Inline ); +bool catenary_scene_file_changed( std::string const &File ); +// zapis scenerii zapisał wpisy pliku +void catenary_scene_file_saved( std::string const &File ); +// wpisy pliku jako linie tekstu (wpisy node), w kolejności, w jakiej sceneria ma je wczytać +std::vector catenary_scene_entries( std::string const &File ); // warstwa scenerii (uchwyt scene::Layers), której plik dołącza plik sieci, a dla pliku założonego w edytorze ta, do której ma // trafić jego dyrektywa; 0 gdy nieznana (wtedy plik scenariusza) std::size_t catenary_scene_file_layer( std::string const &File );