18
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-14 00:48:04 +01:00

feat(editor): the automatic block both ways or mixed, and the lines out on the schematic

A block boundary of a line both ways has, on each track, the block signal of its trains 15 m before the joint and the one of the wrong-track trains 15 m beyond it, named after its twin with N, both on the outside of the line, the templates leaning the other way picked for the N ones, as on the lines of PKP PLK. The mixed block keeps the automatic block for the trains of the track and the semi-automatic one for the wrong-track ones: no N signals, a warning disc the braking distance before the entry signal of the wrong-track trains. A choice under the table of the lines sets either for the double-track lines of the station, kept in the record of the station, and by itself a line takes the mixed block where a warning disc stands for its wrong-track trains and no N signal does. Set the line signals places the N signals or the warning disc and takes the other away, a block boundary drawn places the N signals of a line both ways, and sbl_odstep.inc switches both block signals of a section with its 7th parameter. The name shown at the mouse while drawing takes the letter the block signal will get where its number stands already.

The station schematic shows the lines out to the next stations straightened: their block sections in the colours of their names with their lengths, the joints, the block signals and the warning discs with their km on the outside of the line, the axis in km; Lines fits them in, a section under the mouse tells its name, length and km, and a click on it flies there in the 3d view.
This commit is contained in:
maj00r
2026-10-22 20:47:52 +02:00
parent d090e93c81
commit 4c55951d8a
5 changed files with 616 additions and 123 deletions

View File

@@ -938,7 +938,7 @@ bool editor_mode::signal_heads(std::vector<signal_head> &Heads, bool const Wait)
if (item.lamps.empty()) if (item.lamps.empty())
continue; continue;
auto const kind{kind_index(item.kind)}; auto const kind{kind_index(item.kind)};
Heads.push_back({item.file, item.name, item.kind, item.mount, item.lamps, item.speeds, kind >= 0 && m_signal.chosen[kind] == index, item.aspects}); Heads.push_back({item.file, item.name, item.kind, item.mount, item.lamps, item.speeds, kind >= 0 && m_signal.chosen[kind] == index, item.aspects, item.lean});
} }
return true; return true;
} }

View File

@@ -62,6 +62,7 @@ double constexpr kShpWindow{100.0}; // m, either way of its place, a resonator s
double constexpr kResonatorDrop{0.2}; // m, of its origin below the axis double constexpr kResonatorDrop{0.2}; // m, of its origin below the axis
double constexpr kBoundaryReach{30.0}; // m, either way of a block signal, a click while drawing the boundaries takes it away double constexpr kBoundaryReach{30.0}; // m, either way of a block signal, a click while drawing the boundaries takes it away
double constexpr kBoundaryEnd{50.0}; // m, of a block boundary drawn from the entry signals at the ends of the line double constexpr kBoundaryEnd{50.0}; // m, of a block boundary drawn from the entry signals at the ends of the line
double constexpr kWrongTrack{30.0}; // m, a standing wrong-track block signal counts for the one of a boundary within it of its place
// m, of a train at the speed, as Ie-4 § 8 takes it: the least block section of the automatic block, a warning disc that far before // m, of a train at the speed, as Ie-4 § 8 takes it: the least block section of the automatic block, a warning disc that far before
// its signal // its signal
@@ -630,25 +631,28 @@ int resonator_at(std::vector<station::approach_point> const &Way, double const W
// the template of a block signal of the aspects wanted, Like: the one standing, empty for the one chosen in the Signals tool, taken // the template of a block signal of the aspects wanted, Like: the one standing, empty for the one chosen in the Signals tool, taken
// where it has them, else the one of their mount with them, its counterpart by the name first, then of its lights; not the first of // where it has them, else the one of their mount with them, its counterpart by the name first, then of its lights; not the first of
// a line, whose events tell a station signal; empty for none // a line, whose events tell a station signal; Wrong: of a wrong-track one, standing on the left, its head leaning right where one
std::string block_template(std::vector<signal_head> const &Heads, std::string const &Like, int const Aspects) // does, a head leaning so only for it; empty for none
std::string block_template(std::vector<signal_head> const &Heads, std::string const &Like, int const Aspects, bool const Wrong = false)
{ {
auto const leans = [&](signal_head const &Head) { return (Head.lean == "right") == Wrong; };
auto const any_leans{std::any_of(Heads.begin(), Heads.end(), [&](signal_head const &Head) { return Head.kind == "block" && Head.aspects == Aspects && leans(Head); })};
signal_head const *like{nullptr}; signal_head const *like{nullptr};
for (auto const &head : Heads) for (auto const &head : Heads)
if (head.kind == "block" && (Like.empty() ? head.chosen : head.file == Like)) if (head.kind == "block" && (Like.empty() ? head.chosen : head.file == Like))
like = &head; like = &head;
if (like != nullptr && like->aspects == Aspects) if (like != nullptr && like->aspects == Aspects && (leans(*like) || false == any_leans))
return like->file; return like->file;
auto counterpart{like != nullptr ? like->name : std::string{}}; auto counterpart{like != nullptr ? like->name : std::string{}};
if (auto const digit{counterpart.find_first_of("34")}; digit != std::string::npos) if (auto const digit{counterpart.find_first_of("34")}; digit != std::string::npos)
counterpart[digit] = counterpart[digit] == '3' ? '4' : '3'; counterpart[digit] = counterpart[digit] == '3' ? '4' : '3';
signal_head const *best{nullptr}; signal_head const *best{nullptr};
auto rank{std::numeric_limits<int>::max()}; auto rank{std::make_pair(std::numeric_limits<int>::max(), std::size_t{0})};
for (auto const &head : Heads) for (auto const &head : Heads)
{ {
if (head.kind != "block" || head.aspects != Aspects || ToLower(head.name).find("pierwszy") != std::string::npos) if (head.kind != "block" || head.aspects != Aspects || ToLower(head.name).find("pierwszy") != std::string::npos || (any_leans && false == leans(head)))
continue; continue;
auto const score{like == nullptr ? 0 : (head.mount != like->mount ? 4 : 0) + (head.name != counterpart ? 2 : 0) + (head.lamps != like->lamps ? 1 : 0)}; auto const score{std::make_pair(like == nullptr ? 0 : (head.mount != like->mount ? 4 : 0) + (head.name != counterpart ? 2 : 0) + (head.lamps != like->lamps ? 1 : 0), head.file.size())};
if (score < rank) if (score < rank)
{ {
rank = score; rank = score;
@@ -663,20 +667,49 @@ std::unordered_set<std::string> guarded(std::vector<standing_template> const &Co
{ {
std::unordered_set<std::string> result; std::unordered_set<std::string> result;
for (auto const &control : Controls) for (auto const &control : Controls)
if (control.values.size() > 5) for (std::size_t index = 5; index < control.values.size() && index < 7; ++index)
result.insert(ToLower(control.values[5])); result.insert(ToLower(control.values[index]));
return result; return result;
} }
// a block signal standing by the track of the line facing its trains, Distance: how far out along it // a block signal standing by the track of the line facing its trains, or Wrong: its wrong-track ones, Distance: how far out along it
bool block_along(station::line const &Line, standing_signal const &Signal, double &Distance) bool faces_along(station::line const &Line, standing_signal const &Signal, double &Distance, bool const Wrong = false)
{ {
double gap{0.0}; double gap{0.0};
Distance = station::distance_on(Line, Signal.position, gap); Distance = station::distance_on(Line, Signal.position, gap);
if (Signal.kind != "block" || gap > 4.5 || Distance < 50.0 || Distance > Line.length() - 1.0) if (gap > 4.5 || Distance < 50.0 || Distance > Line.length() - 1.0)
return false; return false;
auto const way{station::point_on(Line, Distance).way}; auto const way{station::point_on(Line, Distance).way};
return (Line.inbound ? -1.0 : 1.0) * glm::dot(travel_of(Signal.yaw), glm::dvec2{way.x, way.z}) >= 0.87; return (Line.inbound != Wrong ? -1.0 : 1.0) * glm::dot(travel_of(Signal.yaw), glm::dvec2{way.x, way.z}) >= 0.87;
}
bool block_along(station::line const &Line, standing_signal const &Signal, double &Distance, bool const Wrong = false)
{
auto const along{faces_along(Line, Signal, Distance, Wrong)};
return Signal.kind == "block" && along;
}
// a warning disc by the line facing its wrong-track trains, on no other line out of the station: of the semi-automatic block they
// run on, the automatic one of the track the other way
bool wrong_disc(std::vector<station::line> const &Lines, std::size_t const Index, standing_signal const &Signal)
{
double distance{0.0};
if (Signal.kind != "warning" || false == faces_along(Lines[Index], Signal, distance, true))
return false;
for (std::size_t other = 0; other < Lines.size(); ++other)
{
double gap{0.0};
auto const at{station::distance_on(Lines[other], Signal.position, gap)};
if (other != Index && gap < 4.5 && at > 50.0 && at < Lines[other].length() - 1.0)
return false;
}
return true;
}
// the way the trains of the line run out along it, 1 out, -1 in
double out_of(station::line const &Line)
{
return Line.inbound ? -1.0 : 1.0;
} }
} // namespace } // namespace
@@ -730,6 +763,15 @@ void srk_tool::load()
} }
from = comma + 1; from = comma + 1;
} }
else if (word == "ways" && words >> word)
for (std::size_t from = 0; from < word.size();)
{
auto const comma{std::min(word.find(',', from), word.size())};
auto const item{word.substr(from, comma - from)};
if (auto const equals{item.find('=')}; equals != std::string::npos && equals > 0)
entry.ways[item.substr(0, equals)] = item.substr(equals + 1) == "2";
from = comma + 1;
}
else if (word == "anchor") else if (word == "anchor")
words >> entry.anchor; words >> entry.anchor;
else if (word == "name") else if (word == "name")
@@ -771,7 +813,11 @@ void srk_tool::store()
for (auto const &[signal, kind] : entry.blocks) for (auto const &[signal, kind] : entry.blocks)
if (false == signal.empty() && signal.find_first_of(" ,=\t") == std::string::npos) if (false == signal.empty() && signal.find_first_of(" ,=\t") == std::string::npos)
blocks += (blocks.empty() ? " blocks " : ",") + signal + (kind == block_kind::four ? "=sbl4" : kind == block_kind::three ? "=sbl" : "=semi"); blocks += (blocks.empty() ? " blocks " : ",") + signal + (kind == block_kind::four ? "=sbl4" : kind == block_kind::three ? "=sbl" : "=semi");
lines.push_back(format("station %s line %s km %s from %s anchor %s%s%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(), braking.c_str(), blocks.c_str(), entry.name.c_str())); std::string ways;
for (auto const &[signal, both] : entry.ways)
if (false == signal.empty() && signal.find_first_of(" ,=\t") == std::string::npos)
ways += (ways.empty() ? " ways " : ",") + signal + (both ? "=2" : "=1");
lines.push_back(format("station %s line %s km %s from %s anchor %s%s%s%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(), braking.c_str(), blocks.c_str(), ways.c_str(), entry.name.c_str()));
} }
scene::Layers.mark(layer, kMark, std::move(lines)); scene::Layers.mark(layer, kMark, std::move(lines));
} }
@@ -1004,24 +1050,29 @@ void srk_tool::draw_overlay() const
drawlist->AddText(ImVec2(screen.x - size.x * 0.5f, screen.y - size.y - 2.0f), kUseful, text.c_str()); drawlist->AddText(ImVec2(screen.x - size.x * 0.5f, screen.y - size.y - 2.0f), kUseful, text.c_str());
} }
} }
// the block boundary under the mouse while they are drawn: a bar across each track and a post where its signal goes // the block boundary under the mouse while they are drawn: a bar across each track at the joint and a post where each signal goes
if (drawing() && m_boundary.valid) if (drawing() && m_boundary.valid)
for (std::size_t k = 0; k < m_boundary.places.size(); ++k)
{ {
auto const colour{m_boundary.remove ? IM_COL32(255, 90, 80, 255) : IM_COL32(110, 230, 120, 255)}; auto const colour{m_boundary.remove ? IM_COL32(255, 90, 80, 255) : IM_COL32(110, 230, 120, 255)};
auto const &way{m_boundary.ways[k]}; for (auto const &[point, way] : m_boundary.joints)
{
glm::dvec3 const across{-way.z, 0.0, way.x}; glm::dvec3 const across{-way.z, 0.0, way.x};
auto const base{m_boundary.places[k] + lift}; projection.line(drawlist, point + lift - across * 2.0, point + lift + across * 2.0, colour, 4.0f);
projection.line(drawlist, base - across * 2.0, base + across * 2.0, colour, 4.0f); }
projection.line(drawlist, base + across * 2.4, base + across * 2.4 + glm::dvec3{0.0, 6.0, 0.0}, colour, 3.0f); for (auto const &post : m_boundary.posts)
{
auto const base{post.point + lift + post.side * 2.4};
projection.line(drawlist, post.point + lift, base, colour, 2.0f);
projection.line(drawlist, base, base + glm::dvec3{0.0, 6.0, 0.0}, colour, 3.0f);
ImVec2 screen; ImVec2 screen;
if (k >= m_boundary.names.size() || false == projection.project(base + across * 2.4 + glm::dvec3{0.0, 6.5, 0.0}, screen)) if (false == projection.project(base + glm::dvec3{0.0, 6.5, 0.0}, screen))
continue; continue;
auto const &text{m_boundary.names[k]}; auto const &text{post.name};
auto const size{ImGui::CalcTextSize(text.c_str())}; auto const size{ImGui::CalcTextSize(text.c_str())};
drawlist->AddRectFilled(ImVec2(screen.x - size.x * 0.5f - 4.0f, screen.y - size.y - 4.0f), ImVec2(screen.x + size.x * 0.5f + 4.0f, screen.y), colour, 3.0f); drawlist->AddRectFilled(ImVec2(screen.x - size.x * 0.5f - 4.0f, screen.y - size.y - 4.0f), ImVec2(screen.x + size.x * 0.5f + 4.0f, screen.y), colour, 3.0f);
drawlist->AddText(ImVec2(screen.x - size.x * 0.5f, screen.y - size.y - 2.0f), IM_COL32(0, 0, 0, 255), text.c_str()); drawlist->AddText(ImVec2(screen.x - size.x * 0.5f, screen.y - size.y - 2.0f), IM_COL32(0, 0, 0, 255), text.c_str());
} }
}
for (auto const &mark : m_signal_marks) for (auto const &mark : m_signal_marks)
{ {
ImVec2 screen; ImVec2 screen;
@@ -1343,6 +1394,14 @@ void srk_tool::render_lines()
} }
m_changed = true; m_changed = true;
}; };
auto const set_ways = [&](line_row const &Row, std::optional<bool> const Both) {
auto &entry{record_of_open()};
if (Both.has_value())
entry.ways[Row.entry] = *Both;
else
entry.ways.erase(Row.entry);
m_changed = true;
};
auto changed{false}; auto changed{false};
if (ImGui::BeginTable("##srklinelist", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) if (ImGui::BeginTable("##srklinelist", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp))
{ {
@@ -1379,6 +1438,8 @@ void srk_tool::render_lines()
ImGui::TextUnformatted(STR_C("A single-track line")); ImGui::TextUnformatted(STR_C("A single-track line"));
ImGui::TextUnformatted(format(STR_P("%zu block signal stands along it", "%zu block signals stand along it", row.blocks), row.blocks).c_str()); ImGui::TextUnformatted(format(STR_P("%zu block signal stands along it", "%zu block signals stand along it", row.blocks), row.blocks).c_str());
ImGui::TextUnformatted(format(STR_C("Block: %s, %s"), kind(row.block), reason(row.why)).c_str()); ImGui::TextUnformatted(format(STR_C("Block: %s, %s"), kind(row.block), reason(row.why)).c_str());
if (row.number > 0 && row.block != block_kind::semi)
ImGui::TextUnformatted(row.both ? STR_C("Wrong-track running: SBL both ways, the block signals with N") : STR_C("Wrong-track running: the mixed block, a warning disc before the entry signal"));
if (row.legacy) if (row.legacy)
ImGui::TextColored(kDoubtful, "%s", STR_C("No block section of the editor switches its block signals: it keeps the block of the scenery as it stands")); ImGui::TextColored(kDoubtful, "%s", STR_C("No block section of the editor switches its block signals: it keeps the block of the scenery as it stands"));
if (row.leftovers > 0) if (row.leftovers > 0)
@@ -1441,13 +1502,42 @@ void srk_tool::render_lines()
} }
ImGui::EndTable(); ImGui::EndTable();
} }
// the wrong-track trains on the automatic block too, or on the semi-automatic one: the mixed block, the double-track lines of the
// automatic block of the station alike
std::vector<line_row const *> two_track;
for (auto const &row : m_lines)
if (row.number > 0 && row.block != block_kind::semi && false == row.legacy)
two_track.push_back(&row);
if (false == two_track.empty())
{
auto const all = [&](bool const Both) { return std::all_of(two_track.begin(), two_track.end(), [&](line_row const *Row) { return Row->both == Both; }); };
int ways{all(true) ? 2 : all(false) ? 1 : 0};
ImGui::TextDisabled("%s", STR_C("Wrong-track running"));
ImGui::BeginDisabled(changed);
if (segmented("##srkways", ways,
{{2, STR_C("SBL both ways"), "At each block boundary the block signals of both tracks either way: of the trains of the track 15 m "
"before the joint, for the wrong-track ones 15 m beyond it, with N (523N), on the outside of the line, as "
"on the lines of PKP PLK"},
{1, STR_C("Mixed"), "The automatic block for the trains of the track, the semi-automatic one for the wrong-track ones: no N "
"signals, a warning disc ToB the braking distance before the entry signal of the wrong-track trains, on the "
"outside of the line. Set the line signals places it and takes the N signals away"}}))
{
for (auto const *row : two_track)
set_ways(*row, (ways == 2) == row->own_both ? std::nullopt : std::optional<bool>{ways == 2});
changed = true;
}
ImGui::EndDisabled();
if (ways == 0)
ImGui::TextDisabled("%s", STR_C("Each line as it stands: a choice sets all of them"));
}
if (changed) if (changed)
survey_signals(); survey_signals();
ImGui::Checkbox(STR_C("Draw the block boundaries"), &m_drawing); ImGui::Checkbox(STR_C("Draw the block boundaries"), &m_drawing);
item_tooltip("LMB on a track of a line of the automatic block, beyond the entry signal: block signals across both tracks there, " item_tooltip("LMB on a track of a line of the automatic block, beyond the entry signal: a block boundary there, the tracks cut "
"numbered after the hectometre ahead of them, the tracks cut 15 m beyond them and the new block sections named; LMB on a " "across the line, block signals 15 m before the joint, numbered after the hectometre ahead of them, on a line both ways "
"block boundary standing takes its signals away, the sections either side joined. A line whose block signals stand keeps " "the wrong-track ones 15 m beyond it, with N, and the new block sections named; LMB on a block boundary standing takes its "
"them: Set the line signals places them evenly only on a line without any. One step of undo each"); "signals away, the sections either side joined. A line whose block signals stand keeps them: Set the line signals places "
"them evenly only on a line without any. One step of undo each");
if (m_drawing) if (m_drawing)
{ {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled));
@@ -1512,29 +1602,49 @@ std::string srk_tool::boundary_hover(glm::dvec3 const &Point, bool &Valid)
else else
next = std::min(next, stop.first); next = std::min(next, stop.first);
} }
// the joint across each track, the block signal of its trains before it, the wrong-track one beyond it, both on the outside
auto const post_at = [&](station::line const &Way, double const Distance, std::string const &Name) {
auto const on{station::point_on(Way, Distance)};
glm::dvec3 const travel{on.way * out_of(Way)};
m_boundary.posts.push_back({on.point, glm::dvec3{-travel.z, 0.0, travel.x}, Name});
};
for (auto const &[index, distance] : tracks) for (auto const &[index, distance] : tracks)
{ {
auto const &other{m_lines[index]}; auto const &other{m_lines[index]};
auto const &way{m_line_ways[index]}; auto const &way{m_line_ways[index]};
auto const drop{station::signal_drop * out_of(way)};
auto joint{distance};
if (m_boundary.remove) if (m_boundary.remove)
{ {
auto const stop{near_stop(other, distance)}; auto const stop{near_stop(other, distance)};
if (stop == other.stops.end()) if (stop == other.stops.end())
continue; continue;
auto const place{station::point_on(way, stop->first)}; joint = stop->first;
m_boundary.places.push_back(place.point); post_at(way, joint - drop, stop->second);
m_boundary.ways.push_back(place.way); auto const wrong{std::min_element(other.wrong.begin(), other.wrong.end(), [&](auto const &A, auto const &B) { return std::abs(A.first - joint - drop) < std::abs(B.first - joint - drop); })};
m_boundary.names.push_back(stop->second); if (wrong != other.wrong.end() && std::abs(wrong->first - joint - drop) < kWrongTrack)
continue; post_at(way, wrong->first, wrong->second);
} }
auto const place{station::point_on(way, distance)}; else
m_boundary.places.push_back(place.point); {
m_boundary.ways.push_back(place.way); // a number standing already gets a letter, as Set the line signals gives it
m_boundary.names.push_back(station::block_number(km_along(way, other.km_number, distance), way.number, other.rising)); auto const free = [&](std::string const &Base) {
auto name{Base};
for (char letter = 'a'; m_signal_names.count(ToLower(name)) > 0 && letter <= 'z'; ++letter)
name = Base + letter;
return name;
};
auto const name{free(station::block_number(km_along(way, other.km_number, joint - drop), way.number, other.rising))};
post_at(way, joint - drop, signal_shown(name, {}));
if (other.both)
post_at(way, joint + drop, signal_shown(free(name + "N"), {}));
}
auto const on{station::point_on(way, joint)};
m_boundary.joints.emplace_back(on.point, on.way);
} }
std::string names; std::string names;
for (auto const &name : m_boundary.names) for (auto const &post : m_boundary.posts)
names += (names.empty() ? "" : ", ") + name; names += (names.empty() ? "" : ", ") + post.name;
m_boundary.valid = true; m_boundary.valid = true;
Valid = true; Valid = true;
if (m_boundary.remove) if (m_boundary.remove)
@@ -2529,6 +2639,31 @@ srk_tool::block_kind srk_tool::block_of(station::line const &Line, std::string c
return Own; return Own;
} }
bool srk_tool::both_ways(std::vector<station::line> const &Lines, std::size_t const Index, std::string const &Entry, std::vector<standing_signal> const &Standing, bool &Hand, bool &Own) const
{
auto const &Line{Lines[Index]};
auto along{false};
auto wrong{false};
auto mixed{false};
for (auto const &signal : Standing)
{
double distance{0.0};
along = along || block_along(Line, signal, distance);
wrong = wrong || block_along(Line, signal, distance, true);
mixed = mixed || wrong_disc(Lines, Index, signal);
}
// block signals for the wrong-track trains, or none of either but a warning disc for them: the mixed block
Own = Line.number > 0 && (wrong || (false == along && false == mixed));
Hand = false;
if (m_record >= 0)
if (auto const set{m_records[m_record].ways.find(Entry)}; set != m_records[m_record].ways.end())
{
Hand = true;
return Line.number > 0 && set->second;
}
return Own;
}
std::string srk_tool::km_line(station::line const &Line) const std::string srk_tool::km_line(station::line const &Line) const
{ {
auto const described{line_of_open()}; auto const described{line_of_open()};
@@ -2603,16 +2738,58 @@ void srk_tool::survey_lines(std::vector<standing_signal> const &Standing)
row.braking = line_braking(line); row.braking = line_braking(line);
row.km_number = km_line(line); row.km_number = km_line(line);
row.rising = km_rising(line, row.km_number); row.rising = km_rising(line, row.km_number);
row.both = both_ways(lines, static_cast<std::size_t>(&line - lines.data()), row.entry, Standing, row.both_hand, row.own_both);
auto switched{false}; auto switched{false};
for (auto const &signal : Standing) for (auto const &signal : Standing)
{ {
double distance{0.0}; double distance{0.0};
if (block_along(line, signal, distance, true))
{
row.wrong.emplace_back(distance, signal_shown(signal.name, stem));
row.posts.push_back({distance, signal_shown(signal.name, stem), true, km_along(line, row.km_number, distance)});
}
if (false == block_along(line, signal, distance)) if (false == block_along(line, signal, distance))
continue; continue;
row.stops.emplace_back(distance, signal_shown(signal.name, stem)); row.stops.emplace_back(distance + station::signal_drop * out_of(line), signal_shown(signal.name, stem));
row.posts.push_back({distance, signal_shown(signal.name, stem), false, km_along(line, row.km_number, distance)});
switched = switched || guards.count(ToLower(signal.name)) > 0; switched = switched || guards.count(ToLower(signal.name)) > 0;
} }
// the warning discs: of the semi-automatic block, of the wrong-track trains of the mixed one
for (auto const &signal : Standing)
{
double distance{0.0};
auto const mixed{row.block != block_kind::semi && wrong_disc(lines, static_cast<std::size_t>(&line - lines.data()), signal)};
if (signal.kind == "warning" && (mixed || (row.block == block_kind::semi && faces_along(line, signal, distance))))
{
faces_along(line, signal, distance, mixed);
row.posts.push_back({distance, signal_shown(signal.name, stem), mixed, km_along(line, row.km_number, distance)});
}
}
std::sort(row.stops.begin(), row.stops.end()); std::sort(row.stops.begin(), row.stops.end());
std::sort(row.wrong.begin(), row.wrong.end());
// the schematic: straight out of the entry signal along the chainage, the track circuits along it in the colours of their names
auto const entry_at{station::project(*m_layout, station::point_on(line, 0.0).point)};
row.chainage = entry_at.x;
row.offset = entry_at.y;
row.outward = station::project(*m_layout, station::point_on(line, std::min(50.0, line.length())).point).x >= entry_at.x ? 1.0 : -1.0;
double probe{0.0};
row.km_known = kilometrage::km_at(row.km_number, station::point_on(line, 0.0).point, probe) || km_of_open(row.chainage, probe);
for (std::size_t k = 0; k < line.points.size() && k < line.distances.size() && k < line.owners.size(); ++k)
{
auto const *track{line.owners[k] < line.tracks.size() ? line.tracks[line.owners[k]] : nullptr};
auto const name{track == nullptr || track->Isolated.empty() ? std::string{} : track->Isolated.front()->asName};
if (row.sections.empty() || row.sections.back().name != name)
row.sections.push_back({line.distances[k], line.distances[k], name});
row.sections.back().to = line.distances[k];
if (k > 0 && row.sections.size() > 1 && row.sections.back().from == line.distances[k])
row.sections[row.sections.size() - 2].to = line.distances[k];
}
row.sections.erase(std::remove_if(row.sections.begin(), row.sections.end(), [](line_row::run const &Run) { return Run.name.empty() || Run.to - std::max(0.0, Run.from) < 1.0; }), row.sections.end());
for (auto &run : row.sections)
{
run.km_from = km_along(line, row.km_number, run.from);
run.km_to = km_along(line, row.km_number, run.to);
}
row.legacy = false == row.stops.empty() && false == switched; row.legacy = false == row.stops.empty() && false == switched;
auto const next{next_entry(line, Standing, row.length)}; auto const next{next_entry(line, Standing, row.length)};
row.next = next >= 0 ? signal_shown(Standing[next].name, stem) : std::string{}; row.next = next >= 0 ? signal_shown(Standing[next].name, stem) : std::string{};
@@ -2630,6 +2807,8 @@ void srk_tool::survey_lines(std::vector<standing_signal> const &Standing)
{ {
if (signal.kind != (automatic ? "warning" : "block") || false == on_line(m_line_ways[index], signal.position)) if (signal.kind != (automatic ? "warning" : "block") || false == on_line(m_line_ways[index], signal.position))
continue; continue;
if (automatic && false == row.both && wrong_disc(m_line_ways, index, signal))
continue;
auto shared{false}; auto shared{false};
for (std::size_t other = 0; other < m_lines.size() && false == shared; ++other) for (std::size_t other = 0; other < m_lines.size() && false == shared; ++other)
shared = (m_lines[other].block != block_kind::semi) != automatic && on_line(m_line_ways[other], signal.position); shared = (m_lines[other].block != block_kind::semi) != automatic && on_line(m_line_ways[other], signal.position);
@@ -2665,7 +2844,9 @@ void srk_tool::set_lines(block_edit const *Edit)
// 0: the semi-automatic one // 0: the semi-automatic one
std::vector<char> automatic(lines.size(), 0); std::vector<char> automatic(lines.size(), 0);
std::vector<int> aspects(lines.size(), 3); std::vector<int> aspects(lines.size(), 3);
std::vector<char> both(lines.size(), 0); // the automatic block both ways
std::vector<std::vector<std::size_t>> along(lines.size()); // the block signals standing along each line facing its trains std::vector<std::vector<std::size_t>> along(lines.size()); // the block signals standing along each line facing its trains
std::vector<std::vector<std::size_t>> wrong(lines.size()); // and facing its wrong-track trains
for (std::size_t index = 0; index < lines.size(); ++index) for (std::size_t index = 0; index < lines.size(); ++index)
{ {
auto const &line{lines[index]}; auto const &line{lines[index]};
@@ -2675,13 +2856,19 @@ void srk_tool::set_lines(block_edit const *Edit)
std::size_t blocks{0}; std::size_t blocks{0};
auto const kind{block_of(line, entries[line.entry].name, standing, heads, why, own, own_why, blocks)}; auto const kind{block_of(line, entries[line.entry].name, standing, heads, why, own, own_why, blocks)};
aspects[index] = kind == block_kind::four ? 4 : 3; aspects[index] = kind == block_kind::four ? 4 : 3;
auto hand{false};
auto own_both{false};
both[index] = both_ways(lines, index, entries[line.entry].name, standing, hand, own_both) ? 1 : 0;
auto switched{false}; auto switched{false};
for (std::size_t k = 0; k < standing.size(); ++k) for (std::size_t k = 0; k < standing.size(); ++k)
{ {
double distance{0.0}; double distance{0.0};
if (false == block_along(line, standing[k], distance)) if (block_along(line, standing[k], distance, true))
continue; wrong[index].push_back(k);
else if (block_along(line, standing[k], distance))
along[index].push_back(k); along[index].push_back(k);
else
continue;
switched = switched || guards.count(ToLower(standing[k].name)) > 0; switched = switched || guards.count(ToLower(standing[k].name)) > 0;
} }
if (kind != block_kind::semi) if (kind != block_kind::semi)
@@ -2739,6 +2926,8 @@ void srk_tool::set_lines(block_edit const *Edit)
std::string linked{"none"}; std::string linked{"none"};
int match{-1}; // of the standing signals int match{-1}; // of the standing signals
bool kept{false}; // standing as it is, its name and its plate kept bool kept{false}; // standing as it is, its name and its plate kept
bool wrong{false}; // facing the wrong-track trains
int twin{-1}; // the block signal of the other way at its boundary, of the planned ones
}; };
struct far_end struct far_end
{ {
@@ -2766,9 +2955,56 @@ void srk_tool::set_lines(block_edit const *Edit)
auto const &end{ends[index]}; auto const &end{ends[index]};
auto const &entry{entries[line.entry]}; auto const &entry{entries[line.entry]};
auto const least{braking(index) * (aspects[index] == 4 ? 0.5 : 1.0)}; auto const least{braking(index) * (aspects[index] == 4 ? 0.5 : 1.0)};
// the block signal of a boundary facing its trains before the joint, the wrong-track one beyond it
auto const drop{station::signal_drop * out_of(line)};
auto const plan = [&](double const Distance, std::string const &Name, int const Match, bool const Wrong) {
planned_signal signal;
signal.name = Name;
signal.plate = Match >= 0 ? std::string{} : Name;
signal.line = index;
signal.distance = Distance;
signal.match = Match;
signal.kept = Match >= 0;
signal.wrong = Wrong;
planned.push_back(std::move(signal));
return static_cast<int>(planned.size() - 1);
};
std::unordered_set<std::size_t> paired;
auto const nearest_wrong = [&](double const Wanted, double &At) {
int found{-1};
auto nearest{kWrongTrack};
for (auto const k : wrong[index])
{
double distance{0.0};
block_along(line, standing[k], distance, true);
if (0 == gone[k] && 0 == paired.count(k) && std::abs(distance - Wanted) < nearest)
{
nearest = std::abs(distance - Wanted);
found = static_cast<int>(k);
At = distance;
}
}
return found;
};
// the wrong-track one: the one standing beyond the joint, else a new one there; none of one way only
auto const add_wrong = [&](int const Normal) {
auto at{planned[Normal].distance + 2.0 * drop};
auto const match{nearest_wrong(at, at)};
if (match >= 0)
paired.insert(static_cast<std::size_t>(match));
if (false == both[index])
{
if (match >= 0)
gone[match] = 1;
return;
}
auto const twin{plan(at, match >= 0 ? standing[match].name : planned[Normal].name + "N", match, true)};
planned[twin].twin = Normal;
planned[Normal].twin = twin;
};
if (automatic[index] == 1) if (automatic[index] == 1)
{ {
// the boundary drawn, on the line drawn on and across on its other track // the boundary drawn, the joint on the line drawn on and across on its other track
std::optional<double> boundary; std::optional<double> boundary;
if (static_cast<int>(index) == edited) if (static_cast<int>(index) == edited)
boundary = drawn; boundary = drawn;
@@ -2781,30 +3017,20 @@ void srk_tool::set_lines(block_edit const *Edit)
// the block signals standing kept as they stand, the boundary drawn added or the ones standing at it taken away // the block signals standing kept as they stand, the boundary drawn added or the ones standing at it taken away
for (auto const k : along[index]) for (auto const k : along[index])
{ {
double gap{0.0}; double distance{0.0};
auto const distance{station::distance_on(line, standing[k].position, gap)}; block_along(line, standing[k], distance);
if (boundary.has_value() && Edit->remove && std::abs(distance - *boundary) < kBoundaryReach) if (boundary.has_value() && Edit->remove && std::abs(distance + drop - *boundary) < kBoundaryReach)
{ {
gone[k] = 1; gone[k] = 1;
auto at{distance + 2.0 * drop};
if (auto const twin{nearest_wrong(at, at)}; twin >= 0)
gone[twin] = 1;
continue; continue;
} }
planned_signal signal; add_wrong(plan(distance, standing[k].name, static_cast<int>(k), false));
signal.name = standing[k].name;
signal.line = index;
signal.distance = distance;
signal.match = static_cast<int>(k);
signal.kept = true;
planned.push_back(std::move(signal));
} }
if (boundary.has_value() && false == Edit->remove) if (boundary.has_value() && false == Edit->remove)
{ add_wrong(plan(*boundary - drop, station::block_number(km_on(index, *boundary - drop), line.number, rising(index)), -1, false));
planned_signal signal;
signal.name = station::block_number(km_on(index, *boundary), line.number, rising(index));
signal.plate = signal.name;
signal.line = index;
signal.distance = *boundary;
planned.push_back(std::move(signal));
}
} }
else else
{ {
@@ -2814,9 +3040,10 @@ void srk_tool::set_lines(block_edit const *Edit)
std::vector<double> places; std::vector<double> places;
if (line.station || end.signal >= 0) if (line.station || end.signal >= 0)
{ {
// as many on both tracks of the line // as many on both tracks of the line, where both have the automatic block
auto const reach{line.partner >= 0 ? (end.distance + ends[line.partner].distance) * 0.5 : end.distance}; auto const paired{line.partner >= 0 && line.partner < static_cast<int>(lines.size()) && automatic[line.partner] == 1};
auto const other{line.partner >= 0 ? braking(line.partner) * (aspects[line.partner] == 4 ? 0.5 : 1.0) : least}; auto const reach{paired ? (end.distance + ends[line.partner].distance) * 0.5 : end.distance};
auto const other{paired ? braking(line.partner) * (aspects[line.partner] == 4 ? 0.5 : 1.0) : least};
auto const sections{std::max(1.0, std::floor(reach / std::max(least, other)))}; auto const sections{std::max(1.0, std::floor(reach / std::max(least, other)))};
for (auto k{1.0}; k < sections; k += 1.0) for (auto k{1.0}; k < sections; k += 1.0)
places.push_back(end.distance * k / sections); places.push_back(end.distance * k / sections);
@@ -2825,19 +3052,18 @@ void srk_tool::set_lines(block_edit const *Edit)
for (auto distance{least * kBlockShare}; distance <= end.distance - 100.0; distance += least * kBlockShare) for (auto distance{least * kBlockShare}; distance <= end.distance - 100.0; distance += least * kBlockShare)
places.push_back(distance); places.push_back(distance);
for (auto const distance : places) for (auto const distance : places)
add_wrong(plan(distance, station::block_number(km_on(index, distance), line.number, rising(index)), -1, false));
}
// the wrong-track ones of no boundary taken away
for (auto const k : wrong[index])
if (0 == paired.count(k))
gone[k] = 1;
}
// the warning disc of the entry signal, the braking distance before it: of the semi-automatic block, or of the mixed one where
// it's the entry signal of the wrong-track trains, on the outside of the line facing them
auto const mixed{automatic[index] == 1 && line.number > 0 && false == line.inbound && false == both[index] && Edit == nullptr};
if (automatic[index] == 0 || mixed)
{ {
planned_signal signal;
signal.name = station::block_number(km_on(index, distance), line.number, rising(index));
signal.plate = signal.name;
signal.line = index;
signal.distance = distance;
planned.push_back(std::move(signal));
}
}
}
else if (automatic[index] == 0)
{
// the warning disc of the entry signal, the braking distance before it
auto const distance{std::min(braking(index), end.distance - 100.0)}; auto const distance{std::min(braking(index), end.distance - 100.0)};
if (distance >= 400.0) if (distance >= 400.0)
{ {
@@ -2847,6 +3073,7 @@ void srk_tool::set_lines(block_edit const *Edit)
signal.plate = "to" + ToLower(entry.name); signal.plate = "to" + ToLower(entry.name);
signal.line = index; signal.line = index;
signal.distance = distance; signal.distance = distance;
signal.wrong = mixed;
planned.push_back(std::move(signal)); planned.push_back(std::move(signal));
} }
} }
@@ -2858,6 +3085,9 @@ void srk_tool::set_lines(block_edit const *Edit)
{ {
if (automatic[index] == 1 ? standing[k].kind != "warning" : std::find(along[index].begin(), along[index].end(), k) == along[index].end()) if (automatic[index] == 1 ? standing[k].kind != "warning" : std::find(along[index].begin(), along[index].end(), k) == along[index].end())
continue; continue;
// the one of the wrong-track trains of the mixed block kept
if (automatic[index] == 1 && false == both[index] && wrong_disc(lines, index, standing[k]))
continue;
// not where the line shares its track with one of the other block // not where the line shares its track with one of the other block
auto shared{false}; auto shared{false};
for (std::size_t other = 0; other < lines.size() && false == shared; ++other) for (std::size_t other = 0; other < lines.size() && false == shared; ++other)
@@ -2900,7 +3130,7 @@ void srk_tool::set_lines(block_edit const *Edit)
double gap{0.0}; double gap{0.0};
auto const distance{station::distance_on(line, standing[k].position, gap)}; auto const distance{station::distance_on(line, standing[k].position, gap)};
auto const facing{facing_out(line, standing[k], distance)}; 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) if (gap < 4.5 && std::abs(distance - signal.distance) < reach && (line.inbound != signal.wrong ? -facing : facing) > 0.87)
candidates.emplace_back(std::abs(distance - signal.distance) + gap * 10.0, i, k); candidates.emplace_back(std::abs(distance - signal.distance) + gap * 10.0, i, k);
} }
} }
@@ -2928,11 +3158,14 @@ void srk_tool::set_lines(block_edit const *Edit)
for (auto const &signal : planned) for (auto const &signal : planned)
if (signal.kept) if (signal.kept)
taken.insert(ToLower(signal.name)); taken.insert(ToLower(signal.name));
// the wrong-track ones after their twins: 523N beside 523
for (auto const wrong_track : {false, true})
for (auto &signal : planned) for (auto &signal : planned)
{ {
if (signal.kind != station::signal::role::block || signal.kept) if (signal.kind != station::signal::role::block || signal.kept || signal.wrong != wrong_track)
continue; continue;
auto const base{signal.name}; auto const base{wrong_track && signal.twin >= 0 ? planned[signal.twin].name + "N" : signal.name};
signal.name = base;
for (char letter = 'a'; taken.count(ToLower(signal.name)) > 0 && letter <= 'z'; ++letter) for (char letter = 'a'; taken.count(ToLower(signal.name)) > 0 && letter <= 'z'; ++letter)
signal.name = base + letter; signal.name = base + letter;
taken.insert(ToLower(signal.name)); taken.insert(ToLower(signal.name));
@@ -2946,6 +3179,7 @@ void srk_tool::set_lines(block_edit const *Edit)
double from{0.0}; double from{0.0};
double to{0.0}; double to{0.0};
std::string guard; // the block signal at its start, empty for a station one std::string guard; // the block signal at its start, empty for a station one
std::string wrong_guard; // the one at its end facing the wrong-track trains, empty for none
glm::dvec3 start{0.0}; glm::dvec3 start{0.0};
}; };
std::vector<section> sections; std::vector<section> sections;
@@ -2964,7 +3198,7 @@ void srk_tool::set_lines(block_edit const *Edit)
}; };
std::vector<stop> order{{0.0, standing[entry_standing[line.entry]].name, -1, static_cast<int>(entry_standing[line.entry])}}; std::vector<stop> order{{0.0, standing[entry_standing[line.entry]].name, -1, static_cast<int>(entry_standing[line.entry])}};
for (std::size_t i = 0; i < planned.size(); ++i) for (std::size_t i = 0; i < planned.size(); ++i)
if (planned[i].line == index) if (planned[i].line == index && false == planned[i].wrong)
order.push_back({planned[i].distance, planned[i].name, static_cast<int>(i), planned[i].match}); order.push_back({planned[i].distance, planned[i].name, static_cast<int>(i), planned[i].match});
if (ends[index].signal >= 0) if (ends[index].signal >= 0)
order.push_back({ends[index].distance, standing[ends[index].signal].name, -1, ends[index].signal}); order.push_back({ends[index].distance, standing[ends[index].signal].name, -1, ends[index].signal});
@@ -2977,6 +3211,8 @@ void srk_tool::set_lines(block_edit const *Edit)
return Stop.distance + (line.inbound ? -station::signal_drop : station::signal_drop); return Stop.distance + (line.inbound ? -station::signal_drop : station::signal_drop);
return Stop.distance + (Stop.distance > 0.0 ? station::signal_drop : -station::signal_drop); return Stop.distance + (Stop.distance > 0.0 ? station::signal_drop : -station::signal_drop);
}; };
// the wrong-track one of a stop, the wrong-track trains pass them the other way
auto const twin_of = [&](std::size_t const At) { return At < order.size() && order[At].signal >= 0 ? planned[order[At].signal].twin : -1; };
for (std::size_t i = 0; i < order.size(); ++i) for (std::size_t i = 0; i < order.size(); ++i)
{ {
auto const &here{order[i]}; auto const &here{order[i]};
@@ -2986,6 +3222,20 @@ void srk_tool::set_lines(block_edit const *Edit)
planned[here.signal].linked = linked; 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)) 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); relinked.emplace_back(here.standing, linked);
auto const wrong_before{twin_of(i + 1)};
auto const wrong_linked{wrong_before >= 0 ? planned[wrong_before].name : std::string{"none"}};
if (auto const twin{twin_of(i)}; twin >= 0)
planned[twin].linked = wrong_linked;
else if (i == 0 && here.signal < 0 && here.standing >= 0 && false == standing[here.standing].linked.empty())
{
// the station signal the wrong-track trains come to: after the last wrong-track block signal, after none once it's gone
auto const &had{standing[here.standing].linked};
auto dropped{false};
for (std::size_t k = 0; k < standing.size() && false == dropped; ++k)
dropped = gone[k] && ToLower(standing[k].name) == ToLower(had);
if (ToLower(had) != ToLower(wrong_linked) && (wrong_before >= 0 || dropped))
relinked.emplace_back(here.standing, wrong_linked);
}
if (i == 0) if (i == 0)
continue; continue;
auto const &before{order[i - 1]}; auto const &before{order[i - 1]};
@@ -2999,6 +3249,8 @@ void srk_tool::set_lines(block_edit const *Edit)
found.from = std::min(joint_of(before), joint_of(here)); found.from = std::min(joint_of(before), joint_of(here));
found.to = std::max(joint_of(before), joint_of(here)); found.to = std::max(joint_of(before), joint_of(here));
found.guard = before.signal >= 0 ? before.name : std::string{}; found.guard = before.signal >= 0 ? before.name : std::string{};
if (auto const twin{twin_of(i)}; twin >= 0)
found.wrong_guard = planned[twin].name;
found.start = station::point_on(line, before.distance).point; found.start = station::point_on(line, before.distance).point;
sections.push_back(std::move(found)); sections.push_back(std::move(found));
} }
@@ -3123,8 +3375,8 @@ void srk_tool::set_lines(block_edit const *Edit)
++renamed; ++renamed;
// of the aspects of its line // of the aspects of its line
auto const head{std::find_if(heads.begin(), heads.end(), [&](signal_head const &Head) { return Head.file == found.file; })}; auto const head{std::find_if(heads.begin(), heads.end(), [&](signal_head const &Head) { return Head.file == found.file; })};
if (block && head != heads.end() && head->aspects != 0 && head->aspects != aspects[signal.line]) if (block && head != heads.end() && head->aspects != 0 && (head->aspects != aspects[signal.line] || (head->lean == "right") != signal.wrong))
if (auto const other{block_template(heads, found.file, aspects[signal.line])}; false == other.empty()) if (auto const other{block_template(heads, found.file, aspects[signal.line], signal.wrong)}; false == other.empty() && other != found.file)
{ {
changes.files.emplace_back(found.instance, other); changes.files.emplace_back(found.instance, other);
++swapped; ++swapped;
@@ -3132,16 +3384,17 @@ void srk_tool::set_lines(block_edit const *Edit)
continue; continue;
} }
auto const &line{lines[signal.line]}; auto const &line{lines[signal.line]};
auto const inbound{line.inbound}; // its trains run in, the wrong-track ones of an outbound track too; a wrong-track one stands on the outside, on their left
auto const inbound{line.inbound != signal.wrong};
auto const at{station::point_on(line, signal.distance)}; auto const at{station::point_on(line, signal.distance)};
auto const approach{station::point_on(line, signal.distance + (inbound ? 1.0 : -1.0))}; auto const approach{station::point_on(line, signal.distance + (inbound ? 1.0 : -1.0))};
signal_order order; signal_order order;
order.track = piece_at(approach.track, approach.point); order.track = piece_at(approach.track, approach.point);
order.point = at.point; order.point = at.point;
order.facing = inbound == approach.outward ? -1 : 1; order.facing = inbound == approach.outward ? -1 : 1;
order.side = order.facing; order.side = signal.wrong ? -order.facing : order.facing;
order.kind = block ? "block" : "warning"; order.kind = block ? "block" : "warning";
order.file = block ? block_template(heads, {}, aspects[signal.line]) : std::string{}; order.file = block ? block_template(heads, {}, aspects[signal.line], signal.wrong) : std::string{};
order.name = signal.name; order.name = signal.name;
order.plate = signal.plate; order.plate = signal.plate;
order.linked = signal.linked; order.linked = signal.linked;
@@ -3154,7 +3407,7 @@ void srk_tool::set_lines(block_edit const *Edit)
if (gone[k]) if (gone[k])
changes.removed.push_back(standing[k].instance); changes.removed.push_back(standing[k].instance);
auto const removed{changes.removed.size()}; auto const removed{changes.removed.size()};
// the block signals switched by the sections they guard // the block signals switched by the sections they guard, the wrong-track one the 7th, or the one guard twice
include_info control; include_info control;
std::string missing; std::string missing;
auto const controlled{editor_includes::load(kControl, control, missing)}; auto const controlled{editor_includes::load(kControl, control, missing)};
@@ -3162,17 +3415,28 @@ void srk_tool::set_lines(block_edit const *Edit)
{ {
for (auto const &found : sections) for (auto const &found : sections)
{ {
if (found.guard.empty())
continue;
auto const already{std::find_if(controls.begin(), controls.end(), [&](standing_template const &Control) { return false == Control.values.empty() && ToLower(Control.values[0]) == found.name; })}; auto const already{std::find_if(controls.begin(), controls.end(), [&](standing_template const &Control) { return false == Control.values.empty() && ToLower(Control.values[0]) == found.name; })};
if (found.guard.empty() && found.wrong_guard.empty())
{
// guarded by station signals only, once its wrong-track signal is gone
if (already != controls.end())
changes.removed.push_back(already->instance);
continue;
}
std::array<std::string, 2> const guards_of{found.guard.empty() ? found.wrong_guard : found.guard, found.wrong_guard.empty() ? found.guard : found.wrong_guard};
if (already != controls.end()) if (already != controls.end())
{
if (already->values.size() > 5 && ToLower(already->values[5]) != ToLower(found.guard))
{ {
auto values{already->values}; auto values{already->values};
values[5] = found.guard; values.resize(std::max<std::size_t>(values.size(), 7));
changes.values.emplace_back(already->instance, std::move(values)); auto differ{false};
for (std::size_t k = 0; k < guards_of.size(); ++k)
if (ToLower(values[5 + k]) != ToLower(guards_of[k]))
{
values[5 + k] = guards_of[k];
differ = true;
} }
if (differ)
changes.values.emplace_back(already->instance, std::move(values));
continue; continue;
} }
template_item item; template_item item;
@@ -3180,7 +3444,8 @@ void srk_tool::set_lines(block_edit const *Edit)
item.location = found.start; item.location = found.start;
item.described = true; item.described = true;
item.values["name"] = found.name; item.values["name"] = found.name;
item.values["#6"] = found.guard; item.values["#6"] = guards_of[0];
item.values["#7"] = guards_of[1];
changes.others.push_back(std::move(item)); changes.others.push_back(std::move(item));
} }
// those of the sections gone from the lines set taken away with them // those of the sections gone from the lines set taken away with them
@@ -3230,8 +3495,11 @@ void srk_tool::set_lines(block_edit const *Edit)
void srk_tool::survey_signals() void srk_tool::survey_signals()
{ {
m_signal_marks.clear(); m_signal_marks.clear();
m_signal_names.clear();
std::vector<standing_signal> standing; std::vector<standing_signal> standing;
m_signals_known = m_layout.has_value() && m_signals.standing_signals(standing, false); m_signals_known = m_layout.has_value() && m_signals.standing_signals(standing, false);
for (auto const &signal : standing)
m_signal_names.insert(ToLower(signal.name));
if (false == m_signals_known) if (false == m_signals_known)
{ {
m_heads.clear(); m_heads.clear();
@@ -3797,6 +4065,12 @@ void srk_tool::fit_view(float const Width, float const Height)
from = std::min(from, mark.at.x); from = std::min(from, mark.at.x);
to = std::max(to, mark.at.x); to = std::max(to, mark.at.x);
} }
if (m_fit_lines)
for (auto const &row : m_lines)
{
from = std::min(from, row.chainage + row.outward * row.length);
to = std::max(to, row.chainage + row.outward * row.length);
}
auto const span{std::max(50.0, to - from)}; auto const span{std::max(50.0, to - from)};
view.from = from - span * 0.04 - 30.0; view.from = from - span * 0.04 - 30.0;
view.to = to + span * 0.04 + 30.0; view.to = to + span * 0.04 + 30.0;
@@ -3872,6 +4146,21 @@ void srk_tool::render_canvas(float const Height)
} }
else if (false == hovered && false == ImGui::IsItemActive()) else if (false == hovered && false == ImGui::IsItemActive())
m_hover = -1; m_hover = -1;
// a line out of the station under the mouse, how far out along it
auto const line_x = [&](line_row const &Row, double const Distance) { return x_of(Row.chainage + Row.outward * Distance); };
int line_hover{-1};
double line_at{0.0};
if (hovered && false == m_dragging && m_hover < 0)
for (std::size_t index = 0; index < m_lines.size() && index < m_line_ways.size() && line_hover < 0; ++index)
{
auto const &row{m_lines[index]};
auto const distance{(view.from + (io.MousePos.x - corner.x) * across - row.chainage) * row.outward};
if (std::abs(io.MousePos.y - y_of(row.offset)) < 6.0f && distance >= 0.0 && distance <= row.length)
{
line_hover = static_cast<int>(index);
line_at = distance;
}
}
m_length_canvas = m_hover >= 0 && false == m_dragging ? length_of(layout.paths[m_hover].track) : -1; m_length_canvas = m_hover >= 0 && false == m_dragging ? length_of(layout.paths[m_hover].track) : -1;
if (ImGui::IsItemDeactivated()) if (ImGui::IsItemDeactivated())
{ {
@@ -3882,6 +4171,12 @@ void srk_tool::render_canvas(float const Height)
m_picked = path.track; m_picked = path.track;
m_view.focus_track(*path.track, path.index, path.points[path.points.size() / 2]); m_view.focus_track(*path.track, path.index, path.points[path.points.size() / 2]);
} }
else if (false == m_dragging && line_hover >= 0)
{
auto const on{station::point_on(m_line_ways[line_hover], line_at)};
auto const out{m_lines[line_hover].inbound ? -1.0 : 1.0};
m_view.fly_to(on.point + glm::dvec3{0.0, 4.0, 0.0}, glm::normalize(glm::dvec3{on.way.x * out, -0.3, on.way.z * out}), 30.0);
}
m_dragging = false; m_dragging = false;
} }
@@ -3896,12 +4191,17 @@ void srk_tool::render_canvas(float const Height)
for (auto const candidate : {10.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0, 2000.0, 5000.0}) for (auto const candidate : {10.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0, 2000.0, 5000.0})
if ((step = candidate) >= metres) if ((step = candidate) >= metres)
break; break;
auto const origin{layout.kilometrage ? layout.km * 1000.0 : 0.0}; // the km of the station where it has them, else the chainage
for (auto mark{std::ceil((view.from + origin) / step) * step}; mark <= view.to + origin; mark += step) double km_from{0.0};
double km_to{0.0};
auto const kilometrage{km_of_open(view.from, km_from) && km_of_open(view.to, km_to) && std::abs(km_to - km_from) > 1e-6};
auto const low{kilometrage ? std::min(km_from, km_to) * 1000.0 : view.from};
auto const high{kilometrage ? std::max(km_from, km_to) * 1000.0 : view.to};
for (auto mark{std::ceil(low / step) * step}; mark <= high; mark += step)
{ {
auto const x{x_of(mark - origin)}; auto const x{x_of(kilometrage ? view.from + (mark / 1000.0 - km_from) / (km_to - km_from) * (view.to - view.from) : mark)};
draw->AddLine(ImVec2(x, corner.y), ImVec2(x, corner_end.y), IM_COL32(255, 255, 255, 16)); draw->AddLine(ImVec2(x, corner.y), ImVec2(x, corner_end.y), IM_COL32(255, 255, 255, 16));
auto const label{layout.kilometrage ? format("%.3f", mark / 1000.0) : format("%.0f m", mark)}; auto const label{kilometrage ? format("km %.3f", mark / 1000.0) : format("%.0f m", mark)};
draw->AddText(ImVec2(x + 3.0f, corner.y + 2.0f), IM_COL32(150, 155, 165, 255), label.c_str()); draw->AddText(ImVec2(x + 3.0f, corner.y + 2.0f), IM_COL32(150, 155, 165, 255), label.c_str());
} }
} }
@@ -3921,6 +4221,29 @@ void srk_tool::render_canvas(float const Height)
auto const at{screen_of(tick)}; auto const at{screen_of(tick)};
draw->AddLine(ImVec2(at.x, at.y - 5.0f), ImVec2(at.x, at.y + 5.0f), IM_COL32(255, 255, 255, 220), 2.0f); draw->AddLine(ImVec2(at.x, at.y - 5.0f), ImVec2(at.x, at.y + 5.0f), IM_COL32(255, 255, 255, 220), 2.0f);
} }
// the lines out straightened beyond the entry signals: the block sections in the colours of their names, the joints between
// them, the block signals each a flag pointing the way of its trains
for (std::size_t index = 0; index < m_lines.size(); ++index)
{
auto const &row{m_lines[index]};
auto const y{y_of(row.offset)};
draw->AddLine(ImVec2(line_x(row, 0.0), y), ImVec2(line_x(row, row.length), y), kApproach, 2.0f);
for (std::size_t k = 0; k < row.sections.size(); ++k)
{
auto const &run{row.sections[k]};
auto const lit{static_cast<int>(index) == line_hover && line_at >= run.from && line_at <= run.to};
draw->AddLine(ImVec2(line_x(row, std::max(0.0, run.from)), y), ImVec2(line_x(row, run.to), y), lit ? kHovered : circuit_colour(run.name), lit ? 4.0f : 2.5f);
for (auto const end : {run.from, run.to})
if (end > 1.0 && end < row.length - 1.0)
draw->AddLine(ImVec2(line_x(row, end), y - 5.0f), ImVec2(line_x(row, end), y + 5.0f), kJoint, 2.0f);
}
for (auto const &post : row.posts)
{
auto const x{line_x(row, post.distance)};
auto const way{static_cast<float>(row.outward * (row.inbound ? -1.0 : 1.0) * (post.wrong ? -1.0 : 1.0))};
draw->AddTriangleFilled(ImVec2(x - 4.0f * way, y - 4.0f), ImVec2(x - 4.0f * way, y + 4.0f), ImVec2(x + 4.0f * way, y), kSignal);
}
}
for (auto const &path : layout.paths) for (auto const &path : layout.paths)
if (path.track == selected) if (path.track == selected)
draw_path(path, overlay_color::selected, 4.0f); draw_path(path, overlay_color::selected, 4.0f);
@@ -3999,6 +4322,27 @@ void srk_tool::render_canvas(float const Height)
draw->AddTriangleFilled(ImVec2(at.x - 4.0f * way, at.y - 4.0f), ImVec2(at.x - 4.0f * way, at.y + 4.0f), ImVec2(at.x + 4.0f * way, at.y), colour); draw->AddTriangleFilled(ImVec2(at.x - 4.0f * way, at.y - 4.0f), ImVec2(at.x - 4.0f * way, at.y + 4.0f), ImVec2(at.x + 4.0f * way, at.y), colour);
label(mark.unfit ? mark.text + " !" : mark.text, colour, {ImVec2(at.x, at.y - height - 5.0f), ImVec2(at.x, at.y + 5.0f)}); label(mark.unfit ? mark.text + " !" : mark.text, colour, {ImVec2(at.x, at.y - height - 5.0f), ImVec2(at.x, at.y + 5.0f)});
} }
// the block signals of the lines with their km, the lengths of the block sections, on the outside of the line: over the upper
// track of a double-track one, under the lower
for (auto const &row : m_lines)
{
auto const y{y_of(row.offset)};
auto const up{row.partner < 0 || row.partner >= static_cast<int>(m_lines.size()) || row.offset >= m_lines[row.partner].offset};
auto const outside = [&](float const Tall, float const Level) { return up ? y - 5.0f - (Tall + 3.0f) * Level + 3.0f : y + 5.0f + (Tall + 3.0f) * (Level - 1.0f); };
auto const tall{height * (row.km_known ? 2.0f : 1.0f)};
for (auto const wrong_track : {false, true})
for (auto const &post : row.posts)
if (post.wrong == wrong_track)
{
auto const x{line_x(row, post.distance)};
label(row.km_known ? post.name + "\n" + format("%.3f", post.km) : post.name, kSignal, {ImVec2(x, outside(tall, 1.0f)), ImVec2(x, outside(tall, 2.0f))});
}
for (auto const &run : row.sections)
{
auto const x{line_x(row, (std::max(0.0, run.from) + run.to) * 0.5)};
label(format("%.2f km", (run.to - std::max(0.0, run.from)) / 1000.0), circuit_colour(run.name), {ImVec2(x, outside(height, 1.0f)), ImVec2(x, outside(height, 2.0f))});
}
}
std::vector<std::size_t> order; std::vector<std::size_t> order;
for (std::size_t index = 0; index < m_circuits.size(); ++index) for (std::size_t index = 0; index < m_circuits.size(); ++index)
if (m_circuits[index].labelled) if (m_circuits[index].labelled)
@@ -4030,6 +4374,25 @@ void srk_tool::render_canvas(float const Height)
ImGui::TextDisabled("%s", STR_C("LMB: select it")); ImGui::TextDisabled("%s", STR_C("LMB: select it"));
ImGui::EndTooltip(); ImGui::EndTooltip();
} }
else if (line_hover >= 0)
{
auto const &row{m_lines[line_hover]};
ImGui::BeginTooltip();
ImGui::TextUnformatted((format(STR_C("The line out beyond the entry signal %s"), row.entry.c_str()) + ", " + (row.number == 0 ? std::string{STR_C("single track")} : format(STR_C("track %d"), row.number))).c_str());
auto const run{std::find_if(row.sections.begin(), row.sections.end(), [&](line_row::run const &Run) { return line_at >= Run.from && line_at <= Run.to; })};
if (run != row.sections.end())
{
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(circuit_colour(run->name)), STR_C("Block section %s, %.2f km"), run->name.c_str(), (run->to - std::max(0.0, run->from)) / 1000.0);
if (row.km_known)
ImGui::TextDisabled(STR_C("km %.3f to %.3f"), std::min(run->km_from, run->km_to), std::max(run->km_from, run->km_to));
}
else
ImGui::TextDisabled("%s", STR_C("No track circuit here"));
if (row.km_known && line_hover < static_cast<int>(m_line_ways.size()))
ImGui::TextDisabled("km %.3f", km_along(m_line_ways[line_hover], row.km_number, line_at));
ImGui::TextDisabled("%s", STR_C("LMB: show it in the 3d view"));
ImGui::EndTooltip();
}
} }
void srk_tool::render_windows() void srk_tool::render_windows()
@@ -4053,9 +4416,21 @@ void srk_tool::render_windows()
if (false == ImGui::IsWindowDocked() && (position.y + size.y > io.DisplaySize.y + 1.0f || position.x + 80.0f > io.DisplaySize.x || position.x + size.x < 80.0f)) if (false == ImGui::IsWindowDocked() && (position.y + size.y > io.DisplaySize.y + 1.0f || position.x + 80.0f > io.DisplaySize.x || position.x + size.x < 80.0f))
ImGui::SetWindowPos(ImVec2(std::clamp(position.x, 0.0f, std::max(0.0f, io.DisplaySize.x - size.x)), std::max(0.0f, std::min(position.y, io.DisplaySize.y - size.y)))); ImGui::SetWindowPos(ImVec2(std::clamp(position.x, 0.0f, std::max(0.0f, io.DisplaySize.x - size.x)), std::max(0.0f, std::min(position.y, io.DisplaySize.y - size.y))));
if (ImGui::Button(STR_C("Fit"))) if (ImGui::Button(STR_C("Fit")))
{
m_fit_lines = false;
m_view_state.exaggeration = 0.0f; m_view_state.exaggeration = 0.0f;
}
item_tooltip("Shows the whole station"); item_tooltip("Shows the whole station");
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(m_lines.empty());
if (ImGui::Button(STR_C("Lines")))
{
m_fit_lines = true;
m_view_state.exaggeration = 0.0f;
}
ImGui::EndDisabled();
item_tooltip("Shows the station with its lines out to the next stations: their block sections, the block signals with their km");
ImGui::SameLine();
ImGui::SetNextItemWidth(140.0f); ImGui::SetNextItemWidth(140.0f);
ImGui::SliderFloat("##srkexaggeration", &m_view_state.exaggeration, 1.0f, 500.0f, STR_C("offsets x%.0f"), ImGuiSliderFlags_Logarithmic); ImGui::SliderFloat("##srkexaggeration", &m_view_state.exaggeration, 1.0f, 500.0f, STR_C("offsets x%.0f"), ImGuiSliderFlags_Logarithmic);
item_tooltip("How much the offsets of the tracks are stretched against the lengths, also Ctrl+wheel over the plot"); item_tooltip("How much the offsets of the tracks are stretched against the lengths, also Ctrl+wheel over the plot");
@@ -4359,7 +4734,8 @@ editor_selftest_result srk_tool::selftest(std::string const &Command, std::istri
} }
if (Command == "srkblock") if (Command == "srkblock")
{ {
// srkblock entry=<A> kind=sbl|sbl4|semi|auto: the block of the line out beyond the entry signal, the lines listed // srkblock entry=<A> [kind=sbl|sbl4|semi|auto] [ways=1|2|auto]: the block of the line out beyond the entry signal, both ways or
// not with the other track of the line, the lines listed
if (m_history.history_revision() != m_history_revision) if (m_history.history_revision() != m_history_revision)
refresh(); refresh();
if (false == m_layout.has_value()) if (false == m_layout.has_value())
@@ -4378,17 +4754,31 @@ editor_selftest_result srk_tool::selftest(std::string const &Command, std::istri
record_of_open().blocks[entry->second] = kind->second == "sbl4" ? block_kind::four : kind->second == "sbl" ? block_kind::three : block_kind::semi; record_of_open().blocks[entry->second] = kind->second == "sbl4" ? block_kind::four : kind->second == "sbl" ? block_kind::three : block_kind::semi;
m_changed = true; m_changed = true;
} }
if (auto const ways{values.find("ways")}; entry != values.end() && ways != values.end())
{
auto const row{std::find_if(m_lines.begin(), m_lines.end(), [&](line_row const &Row) { return Row.entry == entry->second; })};
std::vector<std::string> entries{entry->second};
if (row != m_lines.end() && row->partner >= 0 && row->partner < static_cast<int>(m_lines.size()))
entries.push_back(m_lines[row->partner].entry);
for (auto const &name : entries)
if (ways->second == "auto")
record_of_open().ways.erase(name);
else
record_of_open().ways[name] = ways->second == "2";
m_changed = true;
}
survey_signals(); survey_signals();
if (false == m_lines_problem.empty()) if (false == m_lines_problem.empty())
WriteLog("SELFTEST srkblock: " + m_lines_problem); WriteLog("SELFTEST srkblock: " + m_lines_problem);
for (auto const &row : m_lines) auto const listed = [](std::vector<std::pair<double, std::string>> const &Stops) {
WriteLog(format("SELFTEST srkblock %s track %d %s to %s %.0f m blocks %zu: %s why %d own %d leftovers %zu legacy %d stops%s", row.entry.c_str(), row.number, row.inbound ? "in" : "out", row.next.empty() ? "-" : row.next.c_str(), row.length, row.blocks,
row.block == block_kind::four ? "sbl4" : row.block == block_kind::three ? "sbl" : "semi", static_cast<int>(row.why), static_cast<int>(row.own), row.leftovers, row.legacy ? 1 : 0, [&] {
std::string text; std::string text;
for (auto const &[distance, name] : row.stops) for (auto const &[distance, name] : Stops)
text += format(" %s@%.0f", name.c_str(), distance); text += format(" %s@%.0f", name.c_str(), distance);
return text; return text;
}().c_str())); };
for (auto const &row : m_lines)
WriteLog(format("SELFTEST srkblock %s track %d %s to %s %.0f m blocks %zu: %s why %d own %d ways %d%s leftovers %zu legacy %d stops%s wrong%s", row.entry.c_str(), row.number, row.inbound ? "in" : "out", row.next.empty() ? "-" : row.next.c_str(), row.length, row.blocks,
row.block == block_kind::four ? "sbl4" : row.block == block_kind::three ? "sbl" : "semi", static_cast<int>(row.why), static_cast<int>(row.own), row.both ? 2 : 1, row.both_hand ? " hand" : "", row.leftovers, row.legacy ? 1 : 0, listed(row.stops).c_str(), listed(row.wrong).c_str()));
return editor_selftest_result::handled; return editor_selftest_result::handled;
} }
if (Command == "srkboundary") if (Command == "srkboundary")
@@ -4422,6 +4812,28 @@ editor_selftest_result srk_tool::selftest(std::string const &Command, std::istri
m_drawing = false; m_drawing = false;
return editor_selftest_result::handled; return editor_selftest_result::handled;
} }
if (Command == "srkfit")
{
// srkfit lines|station|from=<m> to=<m>: the schematic fitting the lines out, the station, or the chainage between
std::string word;
std::string mode;
std::map<std::string, std::string> range;
while (Words >> word)
if (auto const equals{word.find('=')}; equals != std::string::npos)
range[word.substr(0, equals)] = word.substr(equals + 1);
else
mode = word;
m_fit_lines = mode == "lines";
m_view_state.exaggeration = 0.0f;
if (range.count("from") > 0 && range.count("to") > 0 && m_layout.has_value())
{
fit_view(1000.0f, 400.0f);
m_view_state.from = std::atof(range.at("from").c_str());
m_view_state.to = std::atof(range.at("to").c_str());
}
WriteLog("SELFTEST srkfit " + mode);
return editor_selftest_result::handled;
}
if (Command == "srkdraw") if (Command == "srkdraw")
{ {
// srkdraw on|off: the block boundaries drawn by hand, or not // srkdraw on|off: the block boundaries drawn by hand, or not
@@ -4462,10 +4874,11 @@ editor_selftest_result srk_tool::selftest(std::string const &Command, std::istri
++bad; ++bad;
WriteLog("SELFTEST srkcheck: block section " + control.values[0] + " twice"); WriteLog("SELFTEST srkcheck: block section " + control.values[0] + " twice");
} }
if (names.count(ToLower(control.values[5])) == 0) for (std::size_t index = 5; index < control.values.size() && index < 7; ++index)
if (names.count(ToLower(control.values[index])) == 0)
{ {
++bad; ++bad;
WriteLog("SELFTEST srkcheck: block section " + control.values[0] + " switched by " + control.values[5] + ", which doesn't stand"); WriteLog("SELFTEST srkcheck: block section " + control.values[0] + " switched by " + control.values[index] + ", which doesn't stand");
} }
} }
WriteLog(format("SELFTEST srkcheck: %zu signals, %zu block sections, %zu bad", standing.size(), controls.size(), bad)); WriteLog(format("SELFTEST srkcheck: %zu signals, %zu block sections, %zu bad", standing.size(), controls.size(), bad));

View File

@@ -80,6 +80,7 @@ class srk_tool final : public editor_tool
bool reversed{false}; // the km rise to the left of the schematic, the switches numbered from there bool reversed{false}; // the km rise to the left of the schematic, the switches numbered from there
int braking{0}; // m, the braking distance of the line set by hand, 0: after its speed int braking{0}; // m, the braking distance of the line set by hand, 0: after its speed
std::map<std::string, block_kind> blocks; // of the lines by their entry signals, as set by hand std::map<std::string, block_kind> blocks; // of the lines by their entry signals, as set by hand
std::map<std::string, bool> ways; // the automatic block of the lines by their entry signals both ways, as set by hand
}; };
// the schematic: the chainage across, the offsets upwards, exaggerated // the schematic: the chainage across, the offsets upwards, exaggerated
struct view struct view
@@ -101,6 +102,7 @@ class srk_tool final : public editor_tool
std::size_t m_history_revision{0}; std::size_t m_history_revision{0};
TTrack const *m_picked{nullptr}; // the track selected when the station was looked for TTrack const *m_picked{nullptr}; // the track selected when the station was looked for
view m_view_state; view m_view_state;
bool m_fit_lines{false}; // the schematic fits the lines out to the next stations too
int m_hover{-1}; // path of the layout under the mouse in the schematic int m_hover{-1}; // path of the layout under the mouse in the schematic
bool m_dragging{false}; bool m_dragging{false};
std::vector<std::pair<std::string, std::size_t>> m_found; // the stations of the scenery: a switch and the count of them std::vector<std::pair<std::string, std::size_t>> m_found; // the stations of the scenery: a switch and the count of them
@@ -193,16 +195,45 @@ class srk_tool final : public editor_tool
std::string next; // the entry signal of the next station facing out along it, shown; empty for none std::string next; // the entry signal of the next station facing out along it, shown; empty for none
bool station{false}; // ends at the next station rather than at the end of the tracks bool station{false}; // ends at the next station rather than at the end of the tracks
std::size_t blocks{0}; // the block signals standing along it std::size_t blocks{0}; // the block signals standing along it
std::vector<std::pair<double, std::string>> stops; // the same, out along it, shown // the block boundaries of the same facing its trains, the joints they turn them to stop at, out along it, with the signal shown
std::vector<std::pair<double, std::string>> stops;
std::vector<std::pair<double, std::string>> wrong; // the block signals facing its wrong-track trains, out along it, shown
block_kind block{block_kind::semi}; block_kind block{block_kind::semi};
block_reason why{block_reason::single}; // the block it has, so block_reason why{block_reason::single}; // the block it has, so
block_kind own{block_kind::semi}; // the block it has by itself, unless set by hand block_kind own{block_kind::semi}; // the block it has by itself, unless set by hand
block_reason own_why{block_reason::single}; block_reason own_why{block_reason::single};
// the automatic block both ways: at each block boundary a block signal of each track either way, the wrong-track one with N
bool both{false};
bool both_hand{false}; // so set by hand
bool own_both{false}; // so by itself
// block signals stand along it, none switched by block sections of the editor: it keeps the block of the scenery as it stands // block signals stand along it, none switched by block sections of the editor: it keeps the block of the scenery as it stands
bool legacy{false}; bool legacy{false};
double braking{0.0}; // m, the braking distance on it double braking{0.0}; // m, the braking distance on it
std::string km_number; // the line of the kilometrage it runs along std::string km_number; // the line of the kilometrage it runs along
bool rising{true}; // its trains run towards the higher km bool rising{true}; // its trains run towards the higher km
bool km_known{false}; // the km out along it are known
// in the schematic, straightened: the chainage of the entry signal, 1 or -1 the way out along the chainage, the offset of the
// track there
double chainage{0.0};
double outward{1.0};
double offset{0.0};
struct run
{
double from{0.0}; // out along the line
double to{0.0};
std::string name;
double km_from{0.0};
double km_to{0.0};
};
std::vector<run> sections; // the track circuits along it, out from the station
struct post
{
double distance{0.0}; // out along the line
std::string name; // shown
bool wrong{false}; // facing the wrong-track trains
double km{0.0};
};
std::vector<post> posts; // the block signals along it
// the signals of the other block standing along it: warning discs on a line of the automatic block, block signals on one of the // the signals of the other block standing along it: warning discs on a line of the automatic block, block signals on one of the
// semi-automatic one, the first of them // semi-automatic one, the first of them
std::size_t leftovers{0}; std::size_t leftovers{0};
@@ -211,6 +242,7 @@ class srk_tool final : public editor_tool
}; };
std::vector<line_row> m_lines; std::vector<line_row> m_lines;
std::vector<station::line> m_line_ways; // of the lines listed std::vector<station::line> m_line_ways; // of the lines listed
std::unordered_set<std::string> m_signal_names; // of the signals of the scenery, lowercase: a block signal drawn on one's number takes a letter
std::string m_lines_problem; // why the station has no lines listed std::string m_lines_problem; // why the station has no lines listed
bool m_drawing{false}; // the block boundaries drawn by hand bool m_drawing{false}; // the block boundaries drawn by hand
// a block boundary under the mouse while they are drawn: block signals across the line there, or the ones standing taken away // a block boundary under the mouse while they are drawn: block signals across the line there, or the ones standing taken away
@@ -219,10 +251,15 @@ class srk_tool final : public editor_tool
bool valid{false}; bool valid{false};
bool remove{false}; bool remove{false};
std::string entry; // of the line under the mouse, its entry signal shown std::string entry; // of the line under the mouse, its entry signal shown
glm::dvec3 point{0.0}; // on its axis glm::dvec3 point{0.0}; // the joint, on its axis
std::vector<glm::dvec3> places; // of the signals, on the axes of the tracks std::vector<std::pair<glm::dvec3, glm::dvec3>> joints; // of each track, on its axis, the way out along it
std::vector<glm::dvec3> ways; // the trains run there struct post
std::vector<std::string> names; {
glm::dvec3 point{0.0}; // on the axis
glm::dvec3 side{1.0, 0.0, 0.0}; // the signal stands off the axis so
std::string name;
};
std::vector<post> posts; // of the block signals
}; };
boundary m_boundary; boundary m_boundary;
// a block boundary drawn on a line of the automatic block of the open station // a block boundary drawn on a line of the automatic block of the open station
@@ -283,6 +320,9 @@ class srk_tool final : public editor_tool
// aspects where one of them has, else on a double-track line, of the aspects of the block signal chosen in the Signals tool, else // aspects where one of them has, else on a double-track line, of the aspects of the block signal chosen in the Signals tool, else
// the semi-automatic one; Own: the block it has by itself // the semi-automatic one; Own: the block it has by itself
block_kind block_of(station::line const &Line, std::string const &Entry, std::vector<standing_signal> const &Standing, std::vector<signal_head> const &Heads, block_reason &Why, block_kind &Own, block_reason &Own_why, std::size_t &Blocks) const; block_kind block_of(station::line const &Line, std::string const &Entry, std::vector<standing_signal> const &Standing, std::vector<signal_head> const &Heads, block_reason &Why, block_kind &Own, block_reason &Own_why, std::size_t &Blocks) const;
// the automatic block of the double-track line both ways: as set by hand for its entry signal, else where block signals stand
// along it, when some of them face its wrong-track trains, else on a new line; Own: so by itself
bool both_ways(std::vector<station::line> const &Lines, std::size_t Index, std::string const &Entry, std::vector<standing_signal> const &Standing, bool &Hand, bool &Own) const;
// the line of the kilometrage a line out of the open station runs along: of the station, at a junction another one // the line of the kilometrage a line out of the open station runs along: of the station, at a junction another one
std::string km_line(station::line const &Line) const; std::string km_line(station::line const &Line) const;
// the km out along the line, Number: of the line of its kilometrage // the km out along the line, Number: of the line of its kilometrage

View File

@@ -117,6 +117,7 @@ struct signal_head
std::vector<int> speeds; // of the signal itself, km/h: -1 the highest allowed, 20 the substitute signal std::vector<int> speeds; // of the signal itself, km/h: -1 the highest allowed, 20 the substitute signal
bool chosen{false}; // the Signals tool places it for its kind bool chosen{false}; // the Signals tool places it for its kind
int aspects{0}; // of a block signal: 3, or 4 with the warning two signals ahead int aspects{0}; // of a block signal: 3, or 4 with the warning two signals ahead
std::string lean; // its head leans towards the track: left, standing on the right of it, right, or none
}; };
// one step of the history of the signalling: signals placed, standing ones renamed, other templates placed with them, the values // one step of the history of the signalling: signals placed, standing ones renamed, other templates placed with them, the values
// of standing ones replaced // of standing ones replaced

View File

@@ -11278,8 +11278,47 @@ msgstr "Blokada szlaku, na obu torach dwutorowego taka sama. Samoczynna (SBL): s
msgid "Draw the block boundaries" msgid "Draw the block boundaries"
msgstr "Rysuj granice odstępów" msgstr "Rysuj granice odstępów"
msgid "LMB on a track of a line of the automatic block, beyond the entry signal: block signals across both tracks there, numbered after the hectometre ahead of them, the tracks cut 15 m beyond them and the new block sections named; LMB on a block boundary standing takes its signals away, the sections either side joined. A line whose block signals stand keeps them: Set the line signals places them evenly only on a line without any. One step of undo each" msgid "LMB on a track of a line of the automatic block, beyond the entry signal: a block boundary there, the tracks cut across the line, block signals 15 m before the joint, numbered after the hectometre ahead of them, on a line both ways the wrong-track ones 15 m beyond it, with N, and the new block sections named; LMB on a block boundary standing takes its signals away, the sections either side joined. A line whose block signals stand keeps them: Set the line signals places them evenly only on a line without any. One step of undo each"
msgstr "LPM na torze szlaku z SBL, za semaforem wjazdowym: semafory SBL na obu torach w tym miejscu, numerowane po hektometrze przed nimi, tory przecięte 15 m za nimi i nazwane nowe odstępy. LPM na istniejącej granicy zabiera jej semafory, a odstępy po obu stronach łączą się w jeden. Szlak, na którym stoją już semafory SBL, je zachowuje: Ustaw sygnalizatory szlakowe rozstawia je równo tylko na szlaku bez żadnego. Każde kliknięcie to jeden krok cofania" msgstr "LPM na torze szlaku z SBL, za semaforem wjazdowym: granica odstępu w tym miejscu, tory przecięte w poprzek szlaku, semafory SBL 15 m przed złączem, numerowane po hektometrze przed nimi, na szlaku w obu kierunkach także semafory dla jazdy po torze lewym 15 m za złączem, z N, i nazwane nowe odstępy. LPM na istniejącej granicy zabiera jej semafory, a odstępy po obu stronach łączą się w jeden. Szlak, na którym stoją już semafory SBL, je zachowuje: Ustaw sygnalizatory szlakowe rozstawia je równo tylko na szlaku bez żadnego. Każde kliknięcie to jeden krok cofania"
msgid "Wrong-track running"
msgstr "Jazda po torze lewym"
msgid "SBL both ways"
msgstr "SBL dwukierunkowa"
msgid "Mixed"
msgstr "Mieszana"
msgid "At each block boundary the block signals of both tracks either way: of the trains of the track 15 m before the joint, for the wrong-track ones 15 m beyond it, with N (523N), on the outside of the line, as on the lines of PKP PLK"
msgstr "Na każdej granicy odstępu semafory SBL obu torów w obu kierunkach: dla jazdy po torze właściwym 15 m przed złączem, dla jazdy po torze lewym 15 m za nim, z N (523N), po zewnętrznej stronie szlaku, jak na liniach PKP PLK"
msgid "The automatic block for the trains of the track, the semi-automatic one for the wrong-track ones: no N signals, a warning disc ToB the braking distance before the entry signal of the wrong-track trains, on the outside of the line. Set the line signals places it and takes the N signals away"
msgstr "SBL dla jazdy po torze właściwym, blokada półsamoczynna dla jazdy po torze lewym: bez semaforów N, tarcza ostrzegawcza ToB w drodze hamowania przed semaforem wjazdowym z toru lewego, po zewnętrznej stronie szlaku. Ustaw sygnalizatory szlakowe ją stawia i zabiera semafory N"
msgid "Each line as it stands: a choice sets all of them"
msgstr "Każdy szlak tak, jak stoi: wybór ustawia wszystkie"
msgid "Wrong-track running: SBL both ways, the block signals with N"
msgstr "Jazda po torze lewym: SBL dwukierunkowa, semafory z N"
msgid "Wrong-track running: the mixed block, a warning disc before the entry signal"
msgstr "Jazda po torze lewym: blokada mieszana, tarcza ostrzegawcza przed semaforem wjazdowym"
msgid "Shows the station with its lines out to the next stations: their block sections, the block signals with their km"
msgstr "Pokazuje stację ze szlakami do sąsiednich stacji: ich odstępy i semafory SBL z kilometrażem"
msgid "Block section %s, %.2f km"
msgstr "Odstęp %s, %.2f km"
msgid "km %.3f to %.3f"
msgstr "km %.3f do %.3f"
msgid "No track circuit here"
msgstr "Tu nie ma odcinka izolowanego"
msgid "LMB: show it in the 3d view"
msgstr "LPM: pokaż w widoku 3d"
msgid "LMB on a track of a line beyond the entry signal: a block boundary there; on one standing: taken away" msgid "LMB on a track of a line beyond the entry signal: a block boundary there; on one standing: taken away"
msgstr "LPM na torze szlaku za semaforem wjazdowym: granica odstępu w tym miejscu; na istniejącej granicy: usunięcie" msgstr "LPM na torze szlaku za semaforem wjazdowym: granica odstępu w tym miejscu; na istniejącej granicy: usunięcie"