From 5f93c7c20d4e46bceaeb574876bdfb9b560cf58b Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Mon, 26 Oct 2026 18:56:28 +0100 Subject: [PATCH] Catenary editor: save the catenary by the active layer --- application/editormode.cpp | 30 +++++++++++++--- application/editormodecatenary.cpp | 55 ++++++++++++++++++++++++++++-- application/editormodecatenary.h | 3 ++ lang/editor_pl.po | 20 +++++++++++ scene/scenelayers.h | 3 +- scene/scenelayerssave.cpp | 12 +++++-- world/catenary_scene.cpp | 19 +++++++++++ world/catenary_scene.h | 4 +++ 8 files changed, 137 insertions(+), 9 deletions(-) diff --git a/application/editormode.cpp b/application/editormode.cpp index 9bc53290..3bd9b774 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -2988,13 +2988,32 @@ bool editor_mode::save() rootstatements.emplace_back("heightmap_terrain " + m_streamer.name() + " endheightmap_terrain"); } + auto const terrainadded{false == rootstatements.empty()}; if (m_profile.changed) profile_store(); switch_store::tidy(); switch_store::settle(); infra_store(); - // the catenary (cantilevers on the poles) lives in files of its own; a file made in the editor needs the directive + // the catenary (cantilevers on the poles) lives in files of its own; a file made in the editor needs the directive, in the file + // of the layer it was made on (the scenario file when that layer can't take it) std::vector catenarynew; + std::map> layerstatements; + auto const same_file = [](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 (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 (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; + } + } if (catenary_scene_changed()) { std::string message; @@ -3007,7 +3026,11 @@ bool editor_mode::save() } for (auto const &file : catenary_scene_new_files()) { - rootstatements.emplace_back("catenary " + file + " endcatenary"); + auto const layer{static_cast(catenary_scene_file_layer(file))}; + if (layer > 1 && scene::Layers.accepts(layer)) + layerstatements[scene::Layers.resolve(layer)].emplace_back("catenary " + file + " endcatenary"); + else + rootstatements.emplace_back("catenary " + file + " endcatenary"); catenarynew.push_back(file); } if (false == m_saved_camera.has_value() || glm::distance(m_saved_camera->first, glm::dvec3{Camera.Pos}) > 0.5 || glm::distance(m_saved_camera->second, glm::vec3{Camera.Angle}) > 0.01f) @@ -3020,7 +3043,6 @@ bool editor_mode::save() m_orthophoto_mark = map; scene::Layers.mark(scene::layer_handle{1}, "//$o", {map}); } - auto const terrainadded{false == rootstatements.empty()}; auto const controls{turntable_controls(rootstatements)}; auto const noses{nose_controls(rootstatements)}; std::vector trailing; @@ -3033,7 +3055,7 @@ 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); + auto const result = scene::Layers.save(rootstatements, trailing, layerstatements); if (result.success && controls) m_turntable_included = true; if (result.success && noses) diff --git a/application/editormodecatenary.cpp b/application/editormodecatenary.cpp index 4325de4a..4e2f66c3 100644 --- a/application/editormodecatenary.cpp +++ b/application/editormodecatenary.cpp @@ -24,6 +24,7 @@ http://mozilla.org/MPL/2.0/. #include "world/Track.h" #include "world/catenary_scene.h" #include "world/catenary_layout.h" +#include "scene/scenelayers.h" #include "imgui/imgui.h" #include "utilities/translation.h" @@ -688,6 +689,35 @@ double catenary_editor::span_recommended(double const Radius) const return wanted > 0.0 ? std::floor(wanted * 2.0) / 2.0 : 0.0; } +void catenary_editor::catenary_file_follow() +{ + auto &tool{m_catenary}; + 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 + 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') + catenary_scene_file_layer(tool.file, active); +} + bool catenary_editor::typed_span() const { auto const &tool{m_catenary}; @@ -4230,9 +4260,29 @@ void catenary_editor::render_catenary_cantilever_ui() ImGui::PushItemWidth(180.0f); if (tool.selected < 0) { - ImGui::InputText(STR_C("Catenary file"), tool.file, sizeof(tool.file)); + 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")); + 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")); + if (tool.file_own) + { + ImGui::SameLine(); + if (ImGui::SmallButton(STR_C("by the layer"))) + tool.file_own = false; + if (ImGui::IsItemHovered()) + 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()); + } render_catenary_chain_state(); } else @@ -4643,6 +4693,7 @@ void catenary_editor::render_catenary_ui() { auto &tool{m_catenary}; catenary_scan(); + catenary_file_follow(); if (tool.selected < 0 && tool.bend_user.has_value()) { tool.bend = *tool.bend_user; diff --git a/application/editormodecatenary.h b/application/editormodecatenary.h index 1048aa93..06e692e1 100644 --- a/application/editormodecatenary.h +++ b/application/editormodecatenary.h @@ -164,9 +164,12 @@ struct catenary_editor float span_length{60.0f}; bool show_staggers{true}; // the staggers written at the poles of the selected chain std::optional bend_user; // "cards for" as the user had it before a pole was selected (the pole's own while it is) + bool file_own{false}; // the catenary file was given by the user: it doesn't follow the active layer }; catenary_tool m_catenary; void catenary_scan(); + // the catenary file of the new poles follows the active layer of the scenery + void catenary_file_follow(); // 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 1f094cfd..4e54a1a9 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -11707,3 +11707,23 @@ msgstr "Kliknij: wysięgnik na słupie kotwowym, karta %04d, wykonanie %d, A = % msgid "Anchor: LMB by the track the cantilever on the anchor pole reaches to; Esc stops" msgstr "Kotwienie: LPM przy torze, do którego sięga wysięgnik na słupie kotwowym; Esc przerywa" + +# scenery editor: catenary - the file of the catenary by the active layer + +msgid "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" +msgstr "Plik w katalogu scenerii, do którego trafiają nowe słupy; nowy plik jest przy zapisie dopisywany do scenerii dyrektywą catenary endcatenary, w pliku aktywnej warstwy. Domyślnie plik sieci aktywnej warstwy albo nowy, nazwany od niej" + +msgid "by the layer" +msgstr "wg warstwy" + +msgid "The catenary file follows the active layer again" +msgstr "Plik sieci znowu idzie za aktywną warstwą" + +msgid "included by %s" +msgstr "dołączony przez %s" + +msgid "new: the save writes it and its directive into %s" +msgstr "nowy: zapis utworzy go i dopisze dyrektywę do %s" + +msgid "The catenary couldn't be saved: its file \"%s\" is a file of the scenery; give the catenary another file" +msgstr "Nie udało się zapisać sieci trakcyjnej: jej plik \"%s\" jest plikiem scenerii; podaj sieci inny plik" diff --git a/scene/scenelayers.h b/scene/scenelayers.h index daf3c587..32fb533a 100644 --- a/scene/scenelayers.h +++ b/scene/scenelayers.h @@ -349,7 +349,8 @@ class node_layers // writes changes made in the editor to the scenery files: changed, created and deleted model instances and // 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 - save_result save(std::vector const &Rootstatements = {}, std::vector const &Trailingstatements = {}); + // 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 = {}); std::string check_sources() const; private: diff --git a/scene/scenelayerssave.cpp b/scene/scenelayerssave.cpp index c869ff7d..72de93e8 100644 --- a/scene/scenelayerssave.cpp +++ b/scene/scenelayerssave.cpp @@ -633,7 +633,7 @@ 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) +save_result node_layers::save(std::vector const &Rootstatements, std::vector const &Trailingstatements, std::map> const &Layerstatements) { save_result result; if (m_layers.empty()) @@ -1455,7 +1455,7 @@ 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()))) + markedtext.count(candidate) != 0 || (candidate == root && (false == Rootstatements.empty() || false == Trailingstatements.empty())) || Layerstatements.count(candidate) != 0) { outputs.emplace_back(candidate); } @@ -1568,6 +1568,14 @@ save_result node_layers::save(std::vector const &Rootstatements, st added.text += statement + eol; } } + if (auto const lookup{Layerstatements.find(output)}; lookup != Layerstatements.end()) + { + for (auto const &statement : lookup->second) + { + ensure_newline(added.text, eol); + added.text += statement + eol; + } + } if (output == root && false == Trailingstatements.empty()) { composition trailing; diff --git a/world/catenary_scene.cpp b/world/catenary_scene.cpp index a09cf2fa..d6f30348 100644 --- a/world/catenary_scene.cpp +++ b/world/catenary_scene.cpp @@ -21,6 +21,7 @@ http://mozilla.org/MPL/2.0/. #include #include "simulation/simulation.h" #include "utilities/Globals.h" +#include "scene/scenelayers.h" #include #include @@ -75,6 +76,8 @@ struct scene_file { bool changed { false }; // są zmiany do zapisania }; 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; // łańcuch sieci z pliku: przęsła od podwieszenia do podwieszenia struct scene_chain { @@ -1713,6 +1716,8 @@ catenary_scene_load( cParser &Input ) { Chains.emplace_back( chain ); rebuild_chain( Chains.back() ); } + // 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 ) { // komentarze z początku pliku, żeby zapis z edytora ich nie zgubił scene_file known; @@ -1831,6 +1836,7 @@ catenary_scene_clear() { Gantries.clear(); Anchors.clear(); Files.clear(); + FileLayers.clear(); Notes.clear(); Resolved = 0; Initialized = false; @@ -2093,6 +2099,19 @@ catenary_scene_file_included( std::string const &File ) { if( auto *file { file_named( File ) }; file != nullptr ) { file->created = false; } } +std::size_t +catenary_scene_file_layer( std::string const &File ) { + auto const found { FileLayers.find( File ) }; + return ( found != FileLayers.end() ? found->second : 0 ); +} + +void +catenary_scene_file_layer( std::string const &File, std::size_t const Layer ) { + // plik już dołączony przez scenerię zostaje przy swojej warstwie + if( auto const *file { file_named( File ) }; file != nullptr && false == file->created ) { return; } + FileLayers[ File ] = Layer; +} + bool catenary_scene_save( std::string &Message ) { diff --git a/world/catenary_scene.h b/world/catenary_scene.h index 35b7397f..9537d794 100644 --- a/world/catenary_scene.h +++ b/world/catenary_scene.h @@ -70,6 +70,10 @@ 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 ); +// 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 ); +void catenary_scene_file_layer( std::string const &File, std::size_t Layer ); // zapisuje zmienione pliki sieci do katalogu scenerii. Message: co zapisano albo co poszło źle bool catenary_scene_save( std::string &Message );