From 46e665c46802a003cdc74f072e90079ee4874c53 Mon Sep 17 00:00:00 2001 From: maj00r Date: Thu, 22 Oct 2026 12:13:22 +0200 Subject: [PATCH] feat(editor): the signalling mode sets the signals of the lines out of a station, the automatic block signals and the block sections to the next station on a double-track line, a warning disc before the entry signal on a single-track one The station keeps its km and the direction of its numbering in its record. The block signals are named after their km, odd on track 1 and even on track 2, the sections after the signals at their ends, and each signal gets the control of its section. --- application/editormode.h | 4 +- application/editormodesignal.cpp | 47 ++- application/editormodesrk.cpp | 624 +++++++++++++++++++++++++++++-- application/editormodesrk.h | 17 +- application/editortool.h | 18 +- editor/editorStation.cpp | 164 ++++++++ editor/editorStation.hpp | 38 +- lang/editor_pl.po | 48 +++ 8 files changed, 919 insertions(+), 41 deletions(-) diff --git a/application/editormode.h b/application/editormode.h index dd42ffba..4b58cad9 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1891,7 +1891,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri // takes them out, as one step of the undo std::size_t remove_templates(std::vector const &Instances) override; bool standing_signals(std::vector &Signals, bool const Wait) override; - std::size_t signal_batch(std::vector const &Placed, std::vector const &Renamed, std::vector> States, std::vector Created, std::string &Error) override; + std::size_t signal_batch(signal_changes const &Changes, std::vector> States, std::vector Created, std::string &Error) override; struct signal_template { @@ -2383,7 +2383,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri hekto_tool m_hekto{*this, *this, *this, *this, *this, *this}; fouling_tool m_fouling{*this, *this, *this, *this, *this}; parallel_tool m_parallel{*this, *this, *this, *this}; - srk_tool m_srk{*this, *this, *this, *this}; + srk_tool m_srk{*this, *this, *this, *this, *this}; std::vector m_tools{&m_gauge, &m_speed, &m_joints, &m_vehicle, &m_hekto, &m_fouling, &m_parallel, &m_srk}; void infra_rebase(); diff --git a/application/editormodesignal.cpp b/application/editormodesignal.cpp index 55ad4950..596f8fea 100644 --- a/application/editormodesignal.cpp +++ b/application/editormodesignal.cpp @@ -883,19 +883,26 @@ bool editor_mode::standing_signals(std::vector &Signals, bool c return false; signal_refresh(true); for (auto const &standing : m_signal.standing) - if (standing.kind >= 0) - Signals.push_back({standing.include.instance, standing.name, kKinds[standing.kind], standing.include.location, standing.include.yaw}); + { + if (standing.kind < 0) + continue; + std::string file; + std::vector values; + auto const *item{signal_template_of(standing.include.file)}; + auto const linked{item != nullptr && item->linked && editor_includes::parse_directive(scene::Layers.directive(standing.include.instance), file, values) && values.size() > 6}; + Signals.push_back({standing.include.instance, standing.name, kKinds[standing.kind], standing.include.location, standing.include.yaw, linked ? values[6] : std::string{}}); + } return true; } -std::size_t editor_mode::signal_batch(std::vector const &Placed, std::vector const &Renamed, std::vector> States, std::vector Created, std::string &Error) +std::size_t editor_mode::signal_batch(signal_changes const &Changes, std::vector> States, std::vector Created, std::string &Error) { auto &tool{m_signal}; signal_scan(); signal_refresh(true); std::vector items; std::vector> reads; - for (auto const &order : Placed) + for (auto const &order : Changes.placed) { auto const kind{kind_index(order.kind)}; auto const *item{kind >= 0 && tool.chosen[kind] >= 0 && tool.chosen[kind] < static_cast(tool.templates.size()) ? &tool.templates[tool.chosen[kind]] : nullptr}; @@ -919,16 +926,22 @@ std::size_t editor_mode::signal_batch(std::vector const &Placed, s if (variant->plate) placing.values["#6"] = plate_for(std::string{}, order.plate); if (variant->linked) - placing.values["#7"] = "none"; + placing.values["#7"] = order.linked.empty() ? std::string{"none"} : order.linked; items.push_back(std::move(placing)); reads.emplace_back(spot, variant->read.empty() ? std::string{} : editor_includes::substitute(variant->read, {order.name})); } // the names passed round among the signals go through ones of their own first, or a read event renamed would be renamed again + items.insert(items.end(), Changes.others.begin(), Changes.others.end()); EditorSnapshot step; step.tracks = std::move(States); std::vector> directives; + std::unordered_set changed; + auto const record = [&](scene::instance_handle const Instance, std::string const &Before) { + if (changed.insert(Instance).second) + directives.emplace_back(Instance, Before); + }; for (auto const temporary : {true, false}) - for (auto const &rename : Renamed) + for (auto const &rename : Changes.renamed) { auto const *standing{signal_standing_of(rename.instance)}; auto const *item{standing != nullptr ? signal_template_of(standing->include.file) : nullptr}; @@ -937,20 +950,36 @@ std::size_t editor_mode::signal_batch(std::vector const &Placed, s std::vector values; if (item == nullptr || false == editor_includes::parse_directive(before, file, values) || values.empty()) continue; + if (temporary && ToLower(values[0]) == ToLower(rename.name)) + continue; auto const read_before{item->read.empty() ? std::string{} : editor_includes::substitute(item->read, values)}; values[0] = rename.name + (temporary ? "~" : ""); - if (item->plate && values.size() > 5) + if (item->plate && values.size() > 5 && false == rename.plate.empty()) values[5] = plate_for(std::string{}, rename.plate); + if (item->linked && values.size() > 6 && false == rename.linked.empty()) + values[6] = rename.linked; auto const read_after{item->read.empty() ? std::string{} : editor_includes::substitute(item->read, values)}; auto const directive{editor_includes::compose_directive(file, values)}; if (directive == before) continue; - if (temporary) - directives.emplace_back(rename.instance, before); + record(rename.instance, before); set_include_directive(rename.instance, directive); if (read_before != read_after) signal_renamed(read_before, read_after, step); } + for (auto const &[instance, values] : Changes.values) + { + auto const before{scene::Layers.directive(instance)}; + std::string file; + std::vector standing; + if (false == editor_includes::parse_directive(before, file, standing)) + continue; + if (auto const directive{editor_includes::compose_directive(file, values)}; directive != before) + { + record(instance, before); + set_include_directive(instance, directive); + } + } std::vector instances; if (Error.empty() && false == items.empty()) { diff --git a/application/editormodesrk.cpp b/application/editormodesrk.cpp index eb11b7f1..90ad37a1 100644 --- a/application/editormodesrk.cpp +++ b/application/editormodesrk.cpp @@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/. #include "application/editoruilayer.h" #include "editor/editorFormat.hpp" #include "editor/editorGeometry.hpp" +#include "editor/editorIncludeInfo.hpp" #include "editor/editorSwitchStore.hpp" #include "editor/editorTrack.hpp" @@ -34,6 +35,7 @@ http://mozilla.org/MPL/2.0/. #include #include #include +#include #include namespace @@ -45,6 +47,13 @@ double constexpr kJointRange{1500.0}; // m, of the joints shown in the scenery double constexpr kLabelRange{700.0}; // m, of the names shown there, farther ones crowd the horizon double constexpr kRailHead{0.18}; // m, above the axis of a path +double constexpr kLineLimit{30000.0}; // m, of a line followed out to the next station +double constexpr kBlock{1400.0}; // m, of a block section of the automatic block +double constexpr kPredicted{305.0}; // m, out from the next station to its entry signal, while it has none +double constexpr kWarning{1000.0}; // m, from a warning disc to its entry signal, the braking distance +double constexpr kWarningFast{1300.0}; // m, the same on lines faster than 120 km/h +char const *const kControl{"sbl_odstep.inc"}; // the events of a block section, switching the block signal guarding it + ImU32 constexpr kPlain{IM_COL32(170, 176, 188, 255)}; ImU32 constexpr kSwitch{IM_COL32(240, 200, 90, 255)}; ImU32 constexpr kApproach{IM_COL32(95, 100, 112, 255)}; @@ -122,6 +131,16 @@ std::string signal_shown(std::string const &Name, std::string const &Stem) return text; } +// a signal in the names of the block sections: a block signal by its number, a station one by its letter, with the number of the +// track: 2411, A2 +std::string section_tag(std::string const &Name, int const Track) +{ + auto const cut{Name.rfind('_')}; + auto tag{cut == std::string::npos ? Name : Name.substr(cut + 1)}; + std::transform(tag.begin(), tag.end(), tag.begin(), [](char const Letter) { return static_cast(std::toupper(static_cast(Letter))); }); + return tag + std::to_string(Track); +} + // the way the trains a signal faces run, in the plan: it stands turned to them glm::dvec2 travel_of(double const Yaw) { @@ -174,6 +193,10 @@ void srk_tool::load() { if (word == "line") words >> entry.line; + else if (word == "km") + words >> entry.km; + else if (word == "from") + entry.reversed = (words >> word) && word == "right"; else if (word == "anchor") words >> entry.anchor; else if (word == "name") @@ -185,6 +208,7 @@ void srk_tool::load() } entry.code = shown(entry.code); entry.line = shown(entry.line); + entry.km = shown(entry.km); entry.track = simulation::Paths.find(entry.anchor); if (false == entry.anchor.empty()) m_records.push_back(std::move(entry)); @@ -209,7 +233,7 @@ void srk_tool::store() continue; if (lines.empty()) lines.emplace_back("# stations described in the track editor, the simulation skips these lines"); - lines.push_back(format("station %s line %s anchor %s name %s", word_of(entry.code).c_str(), word_of(entry.line).c_str(), anchor_of(entry).c_str(), entry.name.c_str())); + lines.push_back(format("station %s line %s km %s from %s anchor %s name %s", word_of(entry.code).c_str(), word_of(entry.line).c_str(), word_of(entry.km).c_str(), entry.reversed ? "right" : "left", anchor_of(entry).c_str(), entry.name.c_str())); } scene::Layers.mark(layer, kMark, std::move(lines)); } @@ -239,6 +263,7 @@ void srk_tool::open(TTrack &Track) m_record = index; } m_anchor = m_record >= 0 ? m_records[m_record].track : m_layout->switches.front(); + m_reversed = m_record >= 0 && m_records[m_record].reversed; survey(); } @@ -494,6 +519,14 @@ void srk_tool::render_options() item_tooltip("The abbreviation of the station: the names of its switches, track circuits and signals begin with it"); text("Line", "##srkline", entry != nullptr ? entry->line : std::string{}, "2", [&](std::string const &Value) { record_of_open().line = word_of(Value) == "-" ? std::string{} : word_of(Value); }); item_tooltip("The number of the railway line the station lies on, as the block sections between the stations are named after it"); + double known{0.0}; + auto const hint{layout.kilometrage && km_of_open((layout.from + layout.to) * 0.5, known) ? format("%.3f", known) : std::string{"189.563"}}; + text("Km", "##srkkm", entry != nullptr ? entry->km : std::string{}, hint.c_str(), [&](std::string const &Value) { + auto km{word_of(Value)}; + std::replace(km.begin(), km.end(), ',', '.'); + record_of_open().km = km == "-" ? std::string{} : km; + }); + item_tooltip("The km of the middle of the station along the line, rising the way the switches are numbered: the block signals of the lines are numbered after the km"); ImGui::EndTable(); } step_heading(1, STR_C("Switch numbers")); @@ -501,7 +534,11 @@ void srk_tool::render_options() if (segmented("##srkfrom", from, {{0, STR_C("From the left"), layout.kilometrage ? "From the lower km, as PKP PLK numbers them" : "From the west end of the station"}, {1, STR_C("From the right"), layout.kilometrage ? "From the higher km" : "From the east end of the station"}})) + { m_reversed = from == 1; + record_of_open().reversed = m_reversed; + m_changed = true; + } if (ImGui::Button(STR_C("Number the switches"), ImVec2(-1.0f, 0.0f))) number_switches(); item_tooltip("Numbers the switches 1, 2, 3... along the station and names them after its code, as on the lines of PKP PLK: bpd_1, " @@ -522,6 +559,14 @@ void srk_tool::render_options() "signal at the end of each station track a train is sent out of, F1, G2 towards the start, R1, P2 towards the end; a " "shunting signal Tm1, Tm2... at the end of a shorter track. A signal standing by the place of one gets its name, none is " "doubled. The kinds of signals chosen in the Signals tool. One step of undo"); + step_heading(4, STR_C("Lines")); + if (ImGui::Button(STR_C("Set the line signals"), ImVec2(-1.0f, 0.0f))) + set_lines(); + item_tooltip("Places the signals of the lines out of the station, from its entry signals on. A double-track line gets the automatic " + "block: block signals evenly to the entry signals of the next station, about 1.4 km apart, numbered after the km, 241 on " + "track 1, 242 on track 2, and the block sections between them named as on the lines of PKP PLK, 2411-2571@LK2.1. A " + "single-track line gets a warning disc ToA 1000 m before the entry signal, 1300 m on a line faster than 120 km/h. Needs " + "the line and the km of the station. Run it at the next station too, it keeps what stands. One step of undo"); if (auto const *selected{m_selection.selected_track()}; selected != nullptr && false == selected->Isolated.empty()) if (auto const number{track_of(selected->Isolated.front()->asName, stem_of_open())}; false == number.empty() && begin_fields("##srktrack")) { @@ -591,6 +636,11 @@ void srk_tool::number_switches() m_status = STR_C("Give the station its code first: the switches are named after it"); return; } + if (m_records[m_record].reversed != m_reversed) + { + m_records[m_record].reversed = m_reversed; + m_changed = true; + } auto const units{station::numbering(*m_layout, m_reversed)}; std::vector> names; std::unordered_set own; @@ -776,6 +826,57 @@ std::string srk_tool::stem_of_open() const return m_record >= 0 && false == m_records[m_record].code.empty() ? station::prefix(m_records[m_record].code) : std::string{}; } +bool srk_tool::km_of_open(double const Chainage, double &Km) const +{ + if (false == m_layout.has_value()) + return false; + if (m_record >= 0 && false == m_records[m_record].km.empty()) + { + char *end{nullptr}; + auto const &text{m_records[m_record].km}; + auto const middle{std::strtod(text.c_str(), &end)}; + if (end != text.c_str()) + { + Km = middle + (Chainage - (m_layout->from + m_layout->to) * 0.5) / 1000.0 * (m_reversed ? -1.0 : 1.0); + return true; + } + } + if (false == m_layout->kilometrage) + return false; + Km = station::km_of(*m_layout, Chainage); + return true; +} + +bool srk_tool::station_tracks(std::vector &Tracks, std::unordered_set &Inside) const +{ + Tracks.clear(); + Inside.clear(); + auto const stem{stem_of_open()}; + if (false == m_layout.has_value() || stem.empty()) + return false; + std::map>> numbers; + for (auto const &path : m_layout->paths) + { + if (path.track->Isolated.empty() || Inside.count(path.track) > 0) + continue; + auto const &name{path.track->Isolated.front()->asName}; + if (name.size() <= stem.size() || name.compare(0, stem.size(), stem) != 0 || entry_circuit(name, stem)) + continue; + Inside.insert(path.track); + if (auto const number{track_of(name, stem)}; false == number.empty()) + numbers[number][name].push_back(path.track); + } + for (auto &[number, circuits] : numbers) + { + station::numbered entry; + entry.number = number; + for (auto &[name, members] : circuits) + entry.circuits.push_back(std::move(members)); + Tracks.push_back(std::move(entry)); + } + return false == Tracks.empty(); +} + void srk_tool::renumber_track(TTrack const &Track, std::string const &Number) { m_status.clear(); @@ -823,34 +924,13 @@ void srk_tool::set_signals() return; } auto const &layout{*m_layout}; - // the station tracks by their numbers, the circuits as the tracks have them - std::map>> numbers; + std::vector tracks; std::unordered_set inside; - for (auto const &path : layout.paths) - { - if (path.track->Isolated.empty() || inside.count(path.track) > 0) - continue; - auto const &name{path.track->Isolated.front()->asName}; - if (name.size() <= stem.size() || name.compare(0, stem.size(), stem) != 0 || entry_circuit(name, stem)) - continue; - inside.insert(path.track); - if (auto const number{track_of(name, stem)}; false == number.empty()) - numbers[number][name].push_back(path.track); - } - if (numbers.empty()) + if (false == station_tracks(tracks, inside)) { m_status = STR_C("Set the track circuits first: the signals stand at their joints"); return; } - std::vector tracks; - for (auto &[number, circuits] : numbers) - { - station::numbered entry; - entry.number = number; - for (auto &[name, members] : circuits) - entry.circuits.push_back(std::move(members)); - tracks.push_back(std::move(entry)); - } auto plan{station::signals(layout, tracks, [&](TTrack const &Track) { return inside.count(&Track) > 0; })}; std::vector standing; m_signals.standing_signals(standing, true); @@ -1013,13 +1093,460 @@ void srk_tool::set_signals() return; } std::string error; - auto const count{m_signals.signal_batch(placed, renamed, std::move(before), std::move(created), error)}; + auto const count{m_signals.signal_batch({placed, renamed, {}, {}}, std::move(before), std::move(created), error)}; if (false == error.empty()) m_status = format(STR_C("The signals couldn't be placed: %s"), error.c_str()); else m_status = format(STR_P("%zu signal placed", "%zu signals placed", count), count) + ", " + format(STR_P("%zu renamed", "%zu renamed", renamed.size()), renamed.size()); } +void srk_tool::set_lines() +{ + m_status.clear(); + auto const stem{stem_of_open()}; + if (stem.empty()) + { + m_status = STR_C("Give the station its code first: the signals are named after it"); + return; + } + auto const &layout{*m_layout}; + std::vector tracks; + std::unordered_set inside; + if (false == station_tracks(tracks, inside)) + { + m_status = STR_C("Set the track circuits first: the signals stand at their joints"); + return; + } + auto const plan{station::signals(layout, tracks, [&](TTrack const &Track) { return inside.count(&Track) > 0; })}; + std::vector standing; + m_signals.standing_signals(standing, true); + // the lines begin at the entry signals standing + std::vector entries; + std::vector entry_standing; + for (auto const &planned : plan) + { + if (planned.kind != station::signal::role::entry) + continue; + auto const found{std::find_if(standing.begin(), standing.end(), [&](standing_signal const &Signal) { return Signal.kind == "main" && ToLower(Signal.name) == stem + ToLower(planned.name); })}; + if (found == standing.end()) + continue; + entries.push_back(planned); + entry_standing.push_back(static_cast(found - standing.begin())); + } + if (entries.empty()) + { + m_status = STR_C("Set the signals first: the lines begin at the entry signals"); + return; + } + auto const lines{station::lines_out(layout, entries, kLineLimit)}; + if (lines.empty()) + { + m_status = STR_C("No line leads out of the station beyond its entry signals"); + return; + } + auto const &described{m_records[m_record]}; + double known{0.0}; + if (std::any_of(lines.begin(), lines.end(), [](station::line const &Line) { return Line.number > 0; }) && (false == km_of_open(0.0, known) || described.line.empty())) + { + m_status = STR_C("Give the station its line and its km first: the block signals and the block sections are named after them"); + return; + } + auto const middle{(layout.from + layout.to) * 0.5}; + struct planned_signal + { + station::signal::role kind{station::signal::role::block}; + std::string name; + std::string plate; + std::size_t line{0}; + double distance{0.0}; // out along the line + std::string linked{"none"}; + int match{-1}; // of the standing signals + }; + struct far_end + { + int signal{-1}; // the entry signal of the next station standing, of the standing signals + double distance{0.0}; // out to it, or to where the signals of the line end + }; + std::vector planned; + std::vector ends(lines.size()); + auto const facing_out = [&](station::line const &Line, standing_signal const &Signal, double const Distance) { + auto const way{station::point_on(Line, Distance).way}; + return glm::dot(travel_of(Signal.yaw), glm::dvec2{way.x, way.z}); + }; + auto const km_on = [&](std::size_t const Index, double const Distance) { + auto const chainage{station::project(layout, lines[Index].points.front()).x}; + double km{0.0}; + km_of_open(chainage < middle ? chainage - Distance : chainage + Distance, km); + return km; + }; + for (std::size_t index = 0; index < lines.size(); ++index) + { + auto const &line{lines[index]}; + auto &end{ends[index]}; + // the entry signal of the next station: the first main signal along the line facing out + for (std::size_t k = 0; k < standing.size(); ++k) + { + if (standing[k].kind != "main") + continue; + double gap{0.0}; + auto const distance{station::distance_on(line, standing[k].position, gap)}; + if (gap > 4.5 || distance < 100.0 || distance > line.length() - 1.0 || facing_out(line, standing[k], distance) < 0.87) + continue; + if (end.signal < 0 || distance < end.distance) + { + end.signal = static_cast(k); + end.distance = distance; + } + } + if (end.signal < 0) + end.distance = line.station ? line.length() - kPredicted : line.length(); + } + for (std::size_t index = 0; index < lines.size(); ++index) + { + auto const &line{lines[index]}; + auto const &end{ends[index]}; + auto const &entry{entries[line.entry]}; + if (line.number > 0) + { + // the block signals: the line to the next station parted evenly, towards the end of the tracks every 1.4 km + std::vector places; + if (line.station || end.signal >= 0) + { + // as many on both tracks of the line + auto const reach{line.partner >= 0 ? (end.distance + ends[line.partner].distance) * 0.5 : end.distance}; + auto const sections{std::max(1L, std::lround(reach / kBlock))}; + for (long k = 1; k < sections; ++k) + places.push_back(end.distance * static_cast(k) / static_cast(sections)); + } + else + for (auto distance{kBlock}; distance <= end.distance - 100.0; distance += kBlock) + places.push_back(distance); + for (auto const distance : places) + { + planned_signal signal; + signal.kind = station::signal::role::block; + signal.name = station::block_number(km_on(index, distance), line.number); + signal.plate = signal.name; + signal.line = index; + signal.distance = distance; + planned.push_back(std::move(signal)); + } + } + else + { + // the warning disc of the entry signal, the braking distance before it + auto const fast{entry.track != nullptr && entry.track->VelocityGet() > 120.0}; + auto const distance{std::min(fast ? kWarningFast : kWarning, end.distance - 100.0)}; + if (distance >= 400.0) + { + planned_signal signal; + signal.kind = station::signal::role::warning; + signal.name = stem + "to" + ToLower(entry.name); + signal.plate = "to" + ToLower(entry.name); + signal.line = index; + signal.distance = distance; + planned.push_back(std::move(signal)); + } + } + } + // the standing ones by their places, then by their names + std::vector used(standing.size(), 0); + for (auto const index : entry_standing) + used[index] = 1; + for (auto const &end : ends) + if (end.signal >= 0) + used[end.signal] = 1; + std::vector> candidates; + std::vector standing_at(standing.size(), 0.0); + for (std::size_t i = 0; i < planned.size(); ++i) + { + auto const &signal{planned[i]}; + auto const &line{lines[signal.line]}; + auto const kind{signal.kind == station::signal::role::block ? "block" : "warning"}; + auto const reach{signal.kind == station::signal::role::block ? 300.0 : 400.0}; + for (std::size_t k = 0; k < standing.size(); ++k) + { + if (standing[k].kind != kind) + continue; + double gap{0.0}; + auto const distance{station::distance_on(line, standing[k].position, gap)}; + auto const facing{facing_out(line, standing[k], distance)}; + if (gap < 4.5 && std::abs(distance - signal.distance) < reach && (line.inbound ? -facing : facing) > 0.87) + candidates.emplace_back(std::abs(distance - signal.distance) + gap * 10.0, i, k); + } + } + std::sort(candidates.begin(), candidates.end()); + for (auto const &[score, i, k] : candidates) + if (planned[i].match < 0 && 0 == used[k]) + { + planned[i].match = static_cast(k); + used[k] = 1; + double gap{0.0}; + planned[i].distance = station::distance_on(lines[planned[i].line], standing[k].position, gap); + } + for (auto &signal : planned) + for (std::size_t k = 0; k < standing.size() && signal.match < 0; ++k) + if (0 == used[k] && ToLower(standing[k].name) == ToLower(signal.name)) + { + signal.match = static_cast(k); + used[k] = 1; + } + // a number another signal of the scenery has already gets a letter + std::unordered_set taken; + for (std::size_t k = 0; k < standing.size(); ++k) + if (0 == used[k]) + taken.insert(ToLower(standing[k].name)); + for (auto &signal : planned) + { + if (signal.kind != station::signal::role::block) + continue; + auto const base{signal.name}; + for (char letter = 'a'; taken.count(ToLower(signal.name)) > 0 && letter <= 'z'; ++letter) + signal.name = base + letter; + taken.insert(ToLower(signal.name)); + signal.plate = signal.name; + } + // the block sections of each track of a double-track line, between the signals in the order the trains pass them + struct section + { + std::string name; + std::size_t line{0}; + double from{0.0}; + double to{0.0}; + std::string guard; // the block signal at its start, empty for a station one + glm::dvec3 start{0.0}; + }; + std::vector
sections; + std::vector> relinked; // standing station signals, the block signal before each + for (std::size_t index = 0; index < lines.size(); ++index) + { + auto const &line{lines[index]}; + if (line.number == 0) + continue; + struct stop + { + double distance{0.0}; + std::string name; + int signal{-1}; // of the planned ones, -1 for a station signal + int standing{-1}; + }; + std::vector order{{0.0, standing[entry_standing[line.entry]].name, -1, static_cast(entry_standing[line.entry])}}; + for (std::size_t i = 0; i < planned.size(); ++i) + if (planned[i].line == index) + order.push_back({planned[i].distance, planned[i].name, static_cast(i), planned[i].match}); + if (ends[index].signal >= 0) + order.push_back({ends[index].distance, standing[ends[index].signal].name, -1, ends[index].signal}); + std::sort(order.begin(), order.end(), [](stop const &A, stop const &B) { return A.distance < B.distance; }); + if (line.inbound) + std::reverse(order.begin(), order.end()); + for (std::size_t i = 0; i < order.size(); ++i) + { + auto const &here{order[i]}; + auto const block_before{i > 0 && order[i - 1].signal >= 0}; + auto const linked{block_before ? order[i - 1].name : std::string{"none"}}; + if (here.signal >= 0) + planned[here.signal].linked = linked; + else if (block_before && here.standing >= 0 && false == standing[here.standing].linked.empty() && ToLower(standing[here.standing].linked) != ToLower(linked)) + relinked.emplace_back(here.standing, linked); + if (i == 0) + continue; + auto const &before{order[i - 1]}; + // the lower km first, as on the lines of PKP PLK: 2411-2571@LK2.1 + auto const ascending{km_on(index, before.distance) <= km_on(index, here.distance)}; + auto const &low{ascending ? before : here}; + auto const &high{ascending ? here : before}; + section found; + found.name = ToLower(section_tag(low.name, line.number) + "-" + section_tag(high.name, line.number) + "@lk" + described.line + "." + std::to_string(line.number)); + found.line = index; + found.from = std::min(before.distance, here.distance); + found.to = std::max(before.distance, here.distance); + found.guard = before.signal >= 0 ? before.name : std::string{}; + found.start = station::point_on(line, before.distance).point; + sections.push_back(std::move(found)); + } + } + // the tracks cut where the sections meet, all of it checked before any is + struct cut + { + TTrack *track{nullptr}; + glm::dvec3 point{0.0}; + }; + std::vector cuts; + auto const add_cut = [&](station::line const &Line, double const Distance) -> bool { + auto const at{station::point_on(Line, Distance)}; + if (at.track == nullptr || std::any_of(cuts.begin(), cuts.end(), [&](cut const &Cut) { return glm::distance(Cut.point, at.point) < 0.5; })) + return true; + auto const &ends_of{at.track->m_paths.front().points}; + if (std::min(glm::distance(at.point, ends_of[segment_data::point::start]), glm::distance(at.point, ends_of[segment_data::point::end])) <= 0.3) + return true; + std::string reason; + if (false == editor_track::can_edit_geometry(*at.track, reason)) + { + m_status = format(STR_C("%s can't be cut: %s"), name_of(*at.track).c_str(), reason.c_str()); + return false; + } + cuts.push_back({at.track, at.point}); + return true; + }; + for (auto const &found : sections) + if (false == add_cut(lines[found.line], found.from) || false == add_cut(lines[found.line], found.to)) + return; + for (auto const &line : lines) + for (auto *part : line.tracks) + if (std::string reason; false == scene::Layers.editable(part, &reason) && std::any_of(sections.begin(), sections.end(), [&](section const &Found) { return &lines[Found.line] == &line; })) + { + m_status = format(STR_C("%s can't be changed: %s"), name_of(*part).c_str(), reason.c_str()); + return; + } + std::vector> before; + std::vector created; + std::unordered_set captured; + std::unordered_map> pieces; // of each track cut, itself and the pieces cut off it + auto const piece_at = [&](TTrack *Track, glm::dvec3 const &Point) { + auto found{pieces.find(Track)}; + if (found == pieces.end()) + return Track; + TTrack *best{Track}; + auto nearest{std::numeric_limits::max()}; + for (auto *piece : found->second) + { + auto const point{editor_track::point_at(*piece, editor_track::nearest_parameter(*piece, Point))}; + if (auto const distance{glm::distance(point, Point)}; distance < nearest) + { + nearest = distance; + best = piece; + } + } + return best; + }; + for (auto const &entry : cuts) + { + auto *track{piece_at(entry.track, entry.point)}; + auto const parameter{editor_track::nearest_parameter(*track, entry.point)}; + auto const &ends_of{track->m_paths.front().points}; + if (std::min(glm::distance(entry.point, ends_of[segment_data::point::start]), glm::distance(entry.point, ends_of[segment_data::point::end])) <= 0.3) + continue; + if (captured.insert(track).second) + before.emplace_back(track, editor_track::capture(*track)); + if (auto *piece{editor_track::split_path(*track, parameter)}) + { + created.push_back(piece); + captured.insert(piece); + auto &list{pieces[entry.track]}; + if (list.empty()) + list.push_back(entry.track); + list.push_back(piece); + } + } + // the sections named, each track by its middle + for (std::size_t index = 0; index < lines.size(); ++index) + { + std::vector parts; + for (auto *track : lines[index].tracks) + { + auto const found{pieces.find(track)}; + if (found == pieces.end()) + parts.push_back(track); + else + parts.insert(parts.end(), found->second.begin(), found->second.end()); + } + for (auto *part : parts) + { + double gap{0.0}; + auto const distance{station::distance_on(lines[index], editor_track::point_at(*part, 0.5), gap)}; + if (gap > 1.0) + continue; + auto const found{std::find_if(sections.begin(), sections.end(), [&](section const &Section) { return Section.line == index && distance > Section.from && distance < Section.to; })}; + if (found == sections.end() || (part->Isolated.size() == 1 && ToLower(part->Isolated.front()->asName) == found->name)) + continue; + if (captured.insert(part).second) + before.emplace_back(part, editor_track::capture(*part)); + editor_track::isolated(*part, {found->name}); + } + } + // the signals placed on the track the trains run on up to them + signal_changes changes; + std::size_t blocks{0}; + std::size_t discs{0}; + for (auto const &signal : planned) + { + auto const block{signal.kind == station::signal::role::block}; + if (signal.match >= 0) + { + auto const &found{standing[signal.match]}; + auto const relink{block && false == found.linked.empty() && ToLower(found.linked) != ToLower(signal.linked)}; + if (ToLower(found.name) != ToLower(signal.name) || relink) + changes.renamed.push_back({found.instance, signal.name, signal.plate, relink ? signal.linked : std::string{}}); + continue; + } + auto const &line{lines[signal.line]}; + auto const inbound{line.inbound}; + auto const at{station::point_on(line, signal.distance)}; + auto const approach{station::point_on(line, signal.distance + (inbound ? 1.0 : -1.0))}; + signal_order order; + order.track = piece_at(approach.track, approach.point); + order.point = at.point; + order.facing = inbound == approach.outward ? -1 : 1; + order.side = order.facing; + order.kind = block ? "block" : "warning"; + order.name = signal.name; + order.plate = signal.plate; + order.linked = signal.linked; + changes.placed.push_back(std::move(order)); + ++(block ? blocks : discs); + } + for (auto const &[index, linked] : relinked) + changes.renamed.push_back({standing[index].instance, standing[index].name, std::string{}, linked}); + // the block signals switched by the sections they guard + include_info control; + std::string missing; + auto const controlled{editor_includes::load(kControl, control, missing)}; + if (controlled) + { + auto const standing_controls{m_templates.standing_templates([](std::string const &File) { return File == kControl; })}; + for (auto const &found : sections) + { + if (found.guard.empty()) + continue; + auto const already{std::find_if(standing_controls.begin(), standing_controls.end(), [&](standing_template const &Control) { return false == Control.values.empty() && ToLower(Control.values[0]) == found.name; })}; + if (already != standing_controls.end()) + { + if (already->values.size() > 5 && ToLower(already->values[5]) != ToLower(found.guard)) + { + auto values{already->values}; + values[5] = found.guard; + changes.values.emplace_back(already->instance, std::move(values)); + } + continue; + } + template_item item; + item.file = kControl; + item.location = found.start; + item.described = true; + item.values["name"] = found.name; + item.values["#6"] = found.guard; + changes.others.push_back(std::move(item)); + } + } + if (changes.placed.empty() && changes.renamed.empty() && changes.others.empty() && changes.values.empty() && before.empty() && created.empty()) + { + m_status = STR_C("The lines are set already"); + return; + } + std::string error; + auto const renamed{changes.renamed.size()}; + m_signals.signal_batch(changes, std::move(before), std::move(created), error); + if (false == error.empty()) + { + m_status = format(STR_C("The signals couldn't be placed: %s"), error.c_str()); + return; + } + m_status = format(STR_P("%zu block signal placed", "%zu block signals placed", blocks), blocks) + ", " + format(STR_P("%zu warning disc", "%zu warning discs", discs), discs) + ", " + + format(STR_P("%zu renamed", "%zu renamed", renamed), renamed) + ", " + format(STR_P("%zu block section", "%zu block sections", sections.size()), sections.size()); + if (false == controlled && std::any_of(sections.begin(), sections.end(), [](section const &Found) { return false == Found.guard.empty(); })) + m_status += "\n" + format(STR_C("%s isn't in the scenery folder: the block signals won't change their lights"), kControl); +} + void srk_tool::survey_signals() { m_signal_marks.clear(); @@ -1497,6 +2024,51 @@ editor_selftest_result srk_tool::selftest(std::string const &Command, std::istri WriteLog(format("SELFTEST srksignals %zu:", m_signal_marks.size()) + list); return editor_selftest_result::handled; } + if (Command == "srklines") + { + // srklines: the signals of the lines out of the open station set + if (false == m_layout.has_value()) + { + WriteLog("SELFTEST srklines: no station open"); + return editor_selftest_result::handled; + } + set_lines(); + WriteLog("SELFTEST srklines: " + m_status); + refresh(); + std::vector tracks; + std::unordered_set inside; + if (false == station_tracks(tracks, inside)) + return editor_selftest_result::handled; + auto const plan{station::signals(*m_layout, tracks, [&](TTrack const &Track) { return inside.count(&Track) > 0; })}; + std::vector standing; + m_signals.standing_signals(standing, true); + for (auto const &line : station::lines_out(*m_layout, plan, kLineLimit)) + { + std::vector> along; + for (auto const &signal : standing) + { + double gap{0.0}; + auto const distance{station::distance_on(line, signal.position, gap)}; + if (gap < 4.5 && distance < line.length()) + along.emplace_back(distance, format("%s(%s,%s)", signal.name.c_str(), signal.kind.c_str(), signal.linked.c_str())); + } + std::string last; + for (std::size_t i = 1; i < line.points.size(); i += 20) + { + auto const *track{line.tracks[line.owners[i]]}; + auto const name{track->Isolated.empty() ? std::string{"-"} : track->Isolated.front()->asName}; + if (name != last) + along.emplace_back(line.distances[i], "[" + name + "]"); + last = name; + } + std::sort(along.begin(), along.end()); + std::string list; + for (auto const &[distance, text] : along) + list += format(" %.0f:%s", distance, text.c_str()); + WriteLog(format("SELFTEST srklines %s track %d %s %.0f m%s:", plan[line.entry].name.c_str(), line.number, line.inbound ? "in" : "out", line.length(), line.station ? " to a station" : "") + list); + } + return editor_selftest_result::handled; + } if (Command == "srkstrip") { // srkstrip shown=0: the schematic hidden, the 3d view whole for a screenshot @@ -1533,6 +2105,8 @@ editor_selftest_result srk_tool::selftest(std::string const &Command, std::istri entry.name = name->second; std::replace(entry.name.begin(), entry.name.end(), '_', ' '); } + if (auto const km{values.find("km")}; km != values.end()) + entry.km = km->second; m_changed = true; WriteLog("SELFTEST srkset: " + title()); return editor_selftest_result::handled; diff --git a/application/editormodesrk.h b/application/editormodesrk.h index 9400f61f..8d0f2be2 100644 --- a/application/editormodesrk.h +++ b/application/editormodesrk.h @@ -17,6 +17,7 @@ http://mozilla.org/MPL/2.0/. #include #include +#include #include #include @@ -24,7 +25,10 @@ http://mozilla.org/MPL/2.0/. class srk_tool final : public editor_tool { public: - srk_tool(editor_selection &Selection, editor_view &View, editor_history &History, editor_signals &Signals) : m_selection{Selection}, m_view{View}, m_history{History}, m_signals{Signals} {} + srk_tool(editor_selection &Selection, editor_view &View, editor_history &History, editor_templates &Templates, editor_signals &Signals) + : m_selection{Selection}, m_view{View}, m_history{History}, m_templates{Templates}, m_signals{Signals} + { + } void update() override; void draw_overlay() const override; void render_options() override; @@ -39,6 +43,7 @@ class srk_tool final : public editor_tool editor_selection &m_selection; editor_view &m_view; editor_history &m_history; + editor_templates &m_templates; editor_signals &m_signals; // a station described: found again by a switch of it struct record @@ -49,6 +54,8 @@ class srk_tool final : public editor_tool std::string code; // the abbreviation of the station, as MMz std::string name; std::string line; // the number of the railway line + std::string km; // of the axis of the station along the line, empty while unknown + bool reversed{false}; // the km rise to the left of the schematic, the switches numbered from there }; // the schematic: the chainage across, the offsets upwards, exaggerated struct view @@ -122,8 +129,16 @@ class srk_tool final : public editor_tool void set_signals(); // the signals standing by the station, for the schematic void survey_signals(); + // the signals of the lines out of the station: on a double-track line the block signals to the next station and the block + // sections between them, on a single-track one a warning disc before the entry signal; one step of the history + void set_lines(); // the names of the switches and the circuits of the open station begin with it, empty while it has no code std::string stem_of_open() const; + // the km at the chainage of the open station: of its record, else of its switches; false while unknown + bool km_of_open(double const Chainage, double &Km) const; + // the station tracks by their numbers with their circuits as the tracks have them, Inside: the tracks of the circuits; false + // while it has none + bool station_tracks(std::vector &Tracks, std::unordered_set &Inside) const; // the circuits of the station track of the track renamed after another number; one step of the history void renumber_track(TTrack const &Track, std::string const &Number); }; diff --git a/application/editortool.h b/application/editortool.h index bbe506da..340899c8 100644 --- a/application/editortool.h +++ b/application/editortool.h @@ -84,12 +84,14 @@ struct signal_order std::string kind; std::string name; std::string plate; // the name on the plate under the head, when the template has one + std::string linked{"none"}; // the signal before it, when the template takes one: a block signal shows its aspects by it }; struct signal_rename { scene::instance_handle instance{0}; std::string name; std::string plate; + std::string linked; // the same, empty to keep the one it has }; struct standing_signal { @@ -98,6 +100,16 @@ struct standing_signal std::string kind; glm::dvec3 position{0.0}; double yaw{0.0}; + std::string linked; // of a template that takes one, else empty +}; +// one step of the history of the signalling: signals placed, standing ones renamed, other templates placed with them, the values +// of standing ones replaced +struct signal_changes +{ + std::vector placed; + std::vector renamed; + std::vector others; + std::vector>> values; }; struct nmt_place @@ -177,9 +189,9 @@ class editor_signals public: // false while the signal templates are still being looked for virtual bool standing_signals(std::vector &Signals, bool const Wait) = 0; - // the signals placed by the templates chosen for their kinds and the standing ones renamed, with the tracks changed before: - // one step of the history. returns: how many were placed - virtual std::size_t signal_batch(std::vector const &Placed, std::vector const &Renamed, std::vector> States, std::vector Created, std::string &Error) = 0; + // the signals placed by the templates chosen for their kinds and the rest of the changes, with the tracks changed before: one + // step of the history. returns: how many signals were placed + virtual std::size_t signal_batch(signal_changes const &Changes, std::vector> States, std::vector Created, std::string &Error) = 0; protected: ~editor_signals() = default; diff --git a/editor/editorStation.cpp b/editor/editorStation.cpp index 66c8016a..ce6b598b 100644 --- a/editor/editorStation.cpp +++ b/editor/editorStation.cpp @@ -1254,4 +1254,168 @@ std::vector signals(layout const &Layout, std::vector const &T return result; } +std::vector lines_out(layout const &Layout, std::vector const &Signals, double const Limit) +{ + std::vector result; + for (std::size_t index = 0; index < Signals.size(); ++index) + { + auto const &entry{Signals[index]}; + if (entry.kind != signal::role::entry || entry.track == nullptr || node(*entry.track)) + continue; + line found; + found.entry = index; + // out where the trains it faces come from + auto *track{entry.track}; + auto outward{entry.facing < 0}; + auto first{true}; + std::unordered_set seen; + while (true) + { + double length{0.0}; + auto points{dense_points(*track, 0, length)}; + if (false == outward) + std::reverse(points.begin(), points.end()); + if (first) + { + auto const nearest{std::min_element(points.begin(), points.end(), [&](glm::dvec3 const &A, glm::dvec3 const &B) { return plan_distance(A, entry.point) < plan_distance(B, entry.point); })}; + points.erase(points.begin(), nearest); + points.front() = entry.point; + } + else + points.erase(points.begin()); + seen.insert(track); + found.tracks.push_back(track); + found.outward.push_back(outward); + for (auto const &point : points) + { + found.distances.push_back(found.points.empty() ? 0.0 : found.distances.back() + plan_distance(found.points.back(), point)); + found.points.push_back(point); + found.owners.push_back(found.tracks.size() - 1); + } + first = false; + if (found.length() > Limit) + break; + auto const end{end_of(*track, 0, outward)}; + auto *next{neighbour(*track, 0, outward)}; + if (next == nullptr || false == rail(*next) || seen.count(next) > 0) + break; + if (node(*next)) + { + found.station = true; + break; + } + int path{0}; + bool atend{false}; + if (false == end_at(*next, end, path, atend)) + break; + track = next; + outward = false == atend; + } + if (found.length() > 1.0) + result.push_back(std::move(found)); + } + // a double-track line: two lines out of the same end 3 to 6.5 m apart all along their first kilometre + auto const middle{(Layout.from + Layout.to) * 0.5}; + auto const start = [&](line const &Line) { return project(Layout, Line.points.front()).x < middle; }; + auto const gap_between = [&](line const &A, line const &B) { + auto const reach{std::min({A.length(), B.length(), 1000.0})}; + auto worst{0.0}; + for (auto const fraction : {0.2, 0.6, 1.0}) + { + double gap{0.0}; + distance_on(B, point_on(A, reach * fraction).point, gap); + worst = std::max(worst, std::abs(gap - 4.5)); + } + return reach < 100.0 ? std::numeric_limits::max() : worst; + }; + // the pairs running side by side the longest first: a siding along a main track for a while is no line + std::vector> pairs; + for (std::size_t i = 0; i < result.size(); ++i) + for (std::size_t j = i + 1; j < result.size(); ++j) + if (start(result[i]) == start(result[j])) + if (auto const gap{gap_between(result[i], result[j])}; gap <= 2.0) + pairs.emplace_back(-std::min(result[i].length(), result[j].length()), gap, i, j); + std::sort(pairs.begin(), pairs.end()); + std::vector paired(result.size(), 0); + for (auto const &[reach_order, gap, i, partner] : pairs) + { + if (paired[i] != 0 || paired[partner] != 0) + continue; + paired[i] = paired[partner] = 1; + result[i].partner = static_cast(partner); + result[partner].partner = static_cast(i); + // the trains towards the station on the track on their right; along the chainage the right one is track 1 + auto const &travel{Signals[result[i].entry].travel}; + glm::dvec2 const right{-travel.z, travel.x}; + auto const reach{std::min({result[i].length(), result[partner].length(), 200.0})}; + auto const here{point_on(result[i], reach).point}; + auto const there{point_on(result[partner], reach).point}; + auto const partner_inbound{glm::dot(glm::dvec2{there.x - here.x, there.z - here.z}, right) > 0.0}; + result[i].inbound = false == partner_inbound; + result[partner].inbound = partner_inbound; + for (auto *found : {&result[i], &result[partner]}) + found->number = found->inbound == start(*found) ? 1 : 2; + } + return result; +} + +on_line point_on(line const &Line, double const Distance) +{ + on_line result; + if (Line.points.empty()) + return result; + auto const after{std::lower_bound(Line.distances.begin(), Line.distances.end(), Distance)}; + auto const i{std::clamp(static_cast(after - Line.distances.begin()), 1, std::max(1, Line.points.size() - 1))}; + if (Line.points.size() < 2) + { + result.track = Line.tracks[Line.owners.front()]; + result.outward = Line.outward[Line.owners.front()]; + result.point = Line.points.front(); + return result; + } + auto const step{Line.distances[i] - Line.distances[i - 1]}; + auto const t{step > 1e-9 ? std::clamp((Distance - Line.distances[i - 1]) / step, 0.0, 1.0) : 0.0}; + result.track = Line.tracks[Line.owners[i]]; + result.outward = Line.outward[Line.owners[i]]; + result.point = glm::mix(Line.points[i - 1], Line.points[i], t); + auto const way{Line.points[i] - Line.points[i - 1]}; + if (glm::length(way) > 1e-9) + result.way = glm::normalize(way); + return result; +} + +double distance_on(line const &Line, glm::dvec3 const &Point, double &Gap) +{ + Gap = std::numeric_limits::max(); + auto result{0.0}; + glm::dvec2 const point{Point.x, Point.z}; + for (std::size_t i = 1; i < Line.points.size(); ++i) + { + glm::dvec2 const a{Line.points[i - 1].x, Line.points[i - 1].z}; + glm::dvec2 const b{Line.points[i].x, Line.points[i].z}; + auto const along{b - a}; + auto const length{glm::length(along)}; + if (length < 1e-9) + continue; + auto const t{std::clamp(glm::dot(point - a, along) / (length * length), 0.0, 1.0)}; + if (auto const gap{glm::distance(point, a + along * t)}; gap < Gap) + { + Gap = gap; + result = Line.distances[i - 1] + length * t; + } + } + return result; +} + +std::string block_number(double const Km, int const Track) +{ + auto const hectometres{std::max(Km, 0.0) * 10.0}; + auto number{static_cast(std::llround(hectometres))}; + if ((number % 2 == 1) != (Track % 2 == 1)) + number += hectometres > static_cast(number) ? 1 : -1; + if (number < 1) + number = Track % 2 == 1 ? 1 : 2; + return std::to_string(number); +} + } // namespace station diff --git a/editor/editorStation.hpp b/editor/editorStation.hpp index f8fcceeb..c61690df 100644 --- a/editor/editorStation.hpp +++ b/editor/editorStation.hpp @@ -122,7 +122,9 @@ struct signal { entry, exit, - shunting + shunting, + block, + warning }; role kind{role::entry}; std::string name; // without the code of the station: A, F1, Tm3 @@ -145,4 +147,38 @@ struct signal // station std::vector signals(layout const &Layout, std::vector const &Tracks, std::function const &Inside); +// a track of a line out beyond an entry signal of the station, to the next station or the end of the tracks +struct line +{ + std::size_t entry{0}; // the entry signal standing on it, of the signals given + int number{0}; // of the track of a double-track line: 1 the one on the right along the chainage, 2 the other; 0 on a single-track line + bool inbound{true}; // its trains run towards the station: on a double-track line the track on their right + bool station{false}; // ends at a switch, of the next station, rather than at the end of the tracks + int partner{-1}; // the other track of a double-track line, of the lines + std::vector tracks; // from the entry signal out + std::vector outward; // of each track: the line runs out towards the end of its path + std::vector points; // along the tracks from the entry signal on, a metre or so apart + std::vector distances; // of the points, from the entry signal + std::vector owners; // of the points, the track each lies on + double length() const { return distances.empty() ? 0.0 : distances.back(); } +}; +// where the line runs at the distance out: on what track, the point on its axis, the way out along the line +struct on_line +{ + TTrack *track{nullptr}; + bool outward{true}; // the way out runs towards the end of the path of the track + glm::dvec3 point{0.0}; + glm::dvec3 way{0.0, 0.0, 1.0}; +}; + +// the lines beyond the entry signals among the signals given, no longer than the limit; two lines out of the same end side by side +// are the tracks of a double-track line +std::vector lines_out(layout const &Layout, std::vector const &Signals, double const Limit); +on_line point_on(line const &Line, double const Distance); +// how far out along the line the point lies, Gap: how far from its axis in the plan +double distance_on(line const &Line, glm::dvec3 const &Point, double &Gap); +// the number of a block signal at the km, as PKP PLK gives them: the km and the hectometre, the last digit odd on track 1, even on +// track 2: 241, 242 +std::string block_number(double const Km, int const Track); + } // namespace station diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 79ca1635..229924d2 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -10488,3 +10488,51 @@ msgstr[0] "%zu przemianowany" msgstr[1] "%zu przemianowane" msgstr[2] "%zu przemianowanych" +msgid "Lines" +msgstr "Szlaki" + +msgid "Set the line signals" +msgstr "Ustaw sygnalizatory szlakowe" + +msgid "Places the signals of the lines out of the station, from its entry signals on. A double-track line gets the automatic block: block signals evenly to the entry signals of the next station, about 1.4 km apart, numbered after the km, 241 on track 1, 242 on track 2, and the block sections between them named as on the lines of PKP PLK, 2411-2571@LK2.1. A single-track line gets a warning disc ToA 1000 m before the entry signal, 1300 m on a line faster than 120 km/h. Needs the line and the km of the station. Run it at the next station too, it keeps what stands. One step of undo" +msgstr "Stawia sygnalizatory szlaków wychodzących ze stacji, od jej semaforów wjazdowych. Szlak dwutorowy dostaje samoczynną blokadę liniową: semafory SBL rozstawione równo do semaforów wjazdowych następnej stacji, co około 1,4 km, numerowane według kilometra, 241 na torze 1, 242 na torze 2, a odstępy między nimi nazwane jak na liniach PKP PLK, 2411-2571@LK2.1. Szlak jednotorowy dostaje tarczę ostrzegawczą ToA 1000 m przed semaforem wjazdowym, 1300 m na linii szybszej niż 120 km/h. Potrzebna linia i kilometr stacji. Uruchom to też na następnej stacji, zostawia to, co już stoi. Jeden krok cofania" + +msgid "Km" +msgstr "Kilometr" + +msgid "The km of the middle of the station along the line, rising the way the switches are numbered: the block signals of the lines are numbered after the km" +msgstr "Kilometr środka stacji na linii, rosnący w stronę numeracji rozjazdów: według kilometra numerowane są semafory SBL na szlakach" + +msgid "Give the station its line and its km first: the block signals and the block sections are named after them" +msgstr "Najpierw podaj linię i kilometr stacji: według nich nazywają się semafory SBL i odstępy" + +msgid "Set the signals first: the lines begin at the entry signals" +msgstr "Najpierw ustaw semafory: szlaki zaczynają się od semaforów wjazdowych" + +msgid "No line leads out of the station beyond its entry signals" +msgstr "Za semaforami wjazdowymi nie wychodzi ze stacji żaden szlak" + +msgid "The lines are set already" +msgstr "Szlaki są już ustawione" + +msgid "%s isn't in the scenery folder: the block signals won't change their lights" +msgstr "Brak %s w katalogu scenery: semafory SBL nie będą zmieniać świateł" + +msgid "%zu block signal placed" +msgid_plural "%zu block signals placed" +msgstr[0] "Postawiono %zu semafor SBL" +msgstr[1] "Postawiono %zu semafory SBL" +msgstr[2] "Postawiono %zu semaforów SBL" + +msgid "%zu warning disc" +msgid_plural "%zu warning discs" +msgstr[0] "%zu tarczę ostrzegawczą" +msgstr[1] "%zu tarcze ostrzegawcze" +msgstr[2] "%zu tarcz ostrzegawczych" + +msgid "%zu block section" +msgid_plural "%zu block sections" +msgstr[0] "%zu odstęp" +msgstr[1] "%zu odstępy" +msgstr[2] "%zu odstępów" +