17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-28 22:47:28 +01:00

refactor(editor): the SRK tool judges the SHP resonator of a signal in one place, missing, beyond the tracks, off its place or placed, and picks the place of a resonator, a joint or a cut, apart from placing it; the nearest point of a track to a signal found coarsely and narrowed down rather than by up to 4000 points. A resonator whose event would land away from its place, the track not cut there, is left out with a note, and the status after a failed placing tells Ctrl+Z takes the cuts back; the lines out of a station emptied while the signal templates are looked for, the layout of the station read only once it's known open

This commit is contained in:
maj00r
2026-10-22 19:06:49 +02:00
parent fc6d1158bd
commit 9ea3628661
3 changed files with 128 additions and 73 deletions

View File

@@ -80,6 +80,8 @@ double constexpr kUnequal{20.0}; // m, the useful lengths of the two ways differ
ImU32 constexpr kUnfit{IM_COL32(255, 190, 40, 255)}; ImU32 constexpr kUnfit{IM_COL32(255, 190, 40, 255)};
double constexpr kFastest{250.0}; // km/h, a track set faster is taken for one without a limit double constexpr kFastest{250.0}; // km/h, a track set faster is taken for one without a limit
ImVec4 constexpr kWrong{1.0f, 0.42f, 0.36f, 1.0f}; ImVec4 constexpr kWrong{1.0f, 0.42f, 0.36f, 1.0f};
ImVec4 constexpr kDoubtful{1.0f, 0.75f, 0.3f, 1.0f};
ImVec4 constexpr kRight{0.45f, 0.85f, 0.45f, 1.0f};
bool is_switch(TTrack const &Track) bool is_switch(TTrack const &Track)
{ {
@@ -425,18 +427,36 @@ double route_reach(double const Braking)
return std::max(3000.0, 3.0 * Braking); return std::max(3000.0, 3.0 * Braking);
} }
// how far the point lies from the path in the plan, Parameter: of the nearest point of it, a metre or so apart // how far the point lies from the path in the plan, Parameter: of the nearest point of it, found among points 8 m or so apart and
// narrowed down between the two by them
double nearest_of(TTrack const &Track, int const Path, glm::dvec3 const &Point, double &Parameter) double nearest_of(TTrack const &Track, int const Path, glm::dvec3 const &Point, double &Parameter)
{ {
geometry::bezier const curve{Track.m_paths[Path]}; geometry::bezier const curve{Track.m_paths[Path]};
auto const count{std::clamp(static_cast<int>(curve.plan_length(16)), 64, 4000)}; auto const gap = [&](double const At) { return geometry::plan_distance(curve.point(At), Point); };
auto const count{std::clamp(static_cast<int>(curve.plan_length(16) / 8.0), 16, 512)};
auto best{std::numeric_limits<double>::max()}; auto best{std::numeric_limits<double>::max()};
for (int k = 0; k <= count; ++k) for (int k = 0; k <= count; ++k)
if (auto const distance{geometry::plan_distance(curve.point(static_cast<double>(k) / count), Point)}; distance < best) if (auto const distance{gap(static_cast<double>(k) / count)}; distance < best)
{ {
best = distance; best = distance;
Parameter = static_cast<double>(k) / count; Parameter = static_cast<double>(k) / count;
} }
auto low{std::max(0.0, Parameter - 1.0 / count)};
auto high{std::min(1.0, Parameter + 1.0 / count)};
for (int step = 0; step < 40; ++step)
{
auto const first{low + (high - low) / 3.0};
auto const second{high - (high - low) / 3.0};
if (gap(first) < gap(second))
high = second;
else
low = first;
}
if (auto const narrowed{(low + high) * 0.5}; gap(narrowed) < best)
{
best = gap(narrowed);
Parameter = narrowed;
}
return best; return best;
} }
@@ -573,8 +593,50 @@ double shp_before(std::vector<station::approach_point> const &Way, glm::dvec3 co
return best; return best;
} }
// m from the point to the nearer end of the track
double end_gap(TTrack const &Track, glm::dvec3 const &Point)
{
auto const &ends{Track.m_paths.front().points};
return std::min(glm::distance(Point, ends[segment_data::point::start]), glm::distance(Point, ends[segment_data::point::end]));
}
// of the way of the trains, the point the SHP resonator wanted m before the signal goes at, within kShpTolerance: a joint of the
// tracks, Joint, its event put on the plain track the trains run into there, else a point of a plain track away from its ends, the
// track cut at it; -1 for none
int resonator_at(std::vector<station::approach_point> const &Way, double const Wanted, bool &Joint)
{
auto best{-1};
auto miss{kShpTolerance + 1e-6};
Joint = false;
for (std::size_t k = 0; k + 1 < Way.size(); ++k)
if (Way[k + 1].track != Way[k].track && Way[k].track->eType == tt_Normal && std::abs(Way[k].distance - Wanted) < miss)
{
miss = std::abs(Way[k].distance - Wanted);
best = static_cast<int>(k);
Joint = true;
}
if (best >= 0)
return best;
for (std::size_t k = 0; k < Way.size(); ++k)
if (Way[k].track->eType == tt_Normal && end_gap(*Way[k].track, Way[k].point) > 0.5 && std::abs(Way[k].distance - Wanted) < miss)
{
miss = std::abs(Way[k].distance - Wanted);
best = static_cast<int>(k);
}
return best;
}
} // namespace } // namespace
srk_tool::shp_state srk_tool::head_check::shp_of() const
{
if (shp < 0.0)
return shp_state::needless;
if (shp_found < 0.0)
return shp_short ? shp_state::beyond : shp_state::missing;
return std::abs(shp_found - shp) > kShpTolerance ? shp_state::off : shp_state::placed;
}
void srk_tool::load() void srk_tool::load()
{ {
if (m_loaded) if (m_loaded)
@@ -950,7 +1012,7 @@ void srk_tool::render_options()
ImGui::Spacing(); ImGui::Spacing();
gesture_rows({{"LMB on a switch", "opens its station"}}); gesture_rows({{"LMB on a switch", "opens its station"}});
if (false == m_status.empty()) if (false == m_status.empty())
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", m_status.c_str()); ImGui::TextColored(kDoubtful, "%s", m_status.c_str());
ImGui::Spacing(); ImGui::Spacing();
render_found(); render_found();
return; return;
@@ -1077,25 +1139,16 @@ void srk_tool::render_options()
"700 m up to 100, 1000 m up to 140, 1300 m above. Needs the line and the km of the station. Run it at the next station too, it " "700 m up to 100, 1000 m up to 140, 1300 m above. Needs the line and the km of the station. Run it at the next station too, it "
"keeps what stands. One step of undo"); "keeps what stands. One step of undo");
step_heading(5, STR_C("SHP")); step_heading(5, STR_C("SHP"));
std::size_t needed{0}; std::array<std::size_t, 5> states{};
std::size_t found{0};
std::size_t off{0};
std::size_t nowhere{0};
for (auto const &head : m_heads) for (auto const &head : m_heads)
if (head.shp >= 0.0) ++states[static_cast<std::size_t>(head.shp_of())];
{ auto const of = [&](shp_state const State) { return states[static_cast<std::size_t>(State)]; };
++needed; auto const needed{m_heads.size() - of(shp_state::needless)};
if (head.shp_found >= 0.0) auto const found{of(shp_state::off) + of(shp_state::placed)};
{ auto const off{of(shp_state::off)};
++found; auto const nowhere{of(shp_state::beyond)};
if (std::abs(head.shp_found - head.shp) > kShpTolerance) ImGui::BeginDisabled(of(shp_state::missing) == 0);
++off; if (ImGui::Button(format("%s (%zu)###srkshp", STR_C("Place the SHP resonators"), of(shp_state::missing)).c_str(), ImVec2(-1.0f, 0.0f)))
}
else if (head.shp_short)
++nowhere;
}
ImGui::BeginDisabled(found + nowhere == needed);
if (ImGui::Button(format("%s (%zu)###srkshp", STR_C("Place the SHP resonators"), needed - found - nowhere).c_str(), ImVec2(-1.0f, 0.0f)))
set_shp(); set_shp();
ImGui::EndDisabled(); ImGui::EndDisabled();
item_tooltip("Places the SHP track resonators the signals checked lack, rezonator.inc, as PKP PLK places them: 200 m before an entry " item_tooltip("Places the SHP track resonators the signals checked lack, rezonator.inc, as PKP PLK places them: 200 m before an entry "
@@ -1122,7 +1175,7 @@ void srk_tool::render_options()
ImGui::EndTable(); ImGui::EndTable();
} }
if (false == m_status.empty()) if (false == m_status.empty())
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", m_status.c_str()); ImGui::TextColored(kDoubtful, "%s", m_status.c_str());
ImGui::Spacing(); ImGui::Spacing();
m_length_hover = -1; m_length_hover = -1;
if (ImGui::CollapsingHeader(format("%s (%zu)###srklengths", STR_C("Track lengths"), m_lengths.size()).c_str(), ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader(format("%s (%zu)###srklengths", STR_C("Track lengths"), m_lengths.size()).c_str(), ImGuiTreeNodeFlags_DefaultOpen))
@@ -1354,7 +1407,7 @@ void srk_tool::render_lengths()
} }
ImGui::TextDisabled(STR_C("Construction length %.0f m: between the switches at its ends, the switches within it left out"), track.construction); ImGui::TextDisabled(STR_C("Construction length %.0f m: between the switches at its ends, the switches within it left out"), track.construction);
if (std::abs(track.ways[0].length - track.ways[1].length) > kUnequal) if (std::abs(track.ways[0].length - track.ways[1].length) > kUnequal)
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", STR_C("The two ways differ: their trains stop on other stretches of the track, between other switches or signals")); ImGui::TextColored(kDoubtful, "%s", STR_C("The two ways differ: their trains stop on other stretches of the track, between other switches or signals"));
ImGui::TextDisabled("%s", STR_C("LMB: show it")); ImGui::TextDisabled("%s", STR_C("LMB: show it"));
ImGui::EndTooltip(); ImGui::EndTooltip();
} }
@@ -1415,7 +1468,7 @@ void srk_tool::render_heads()
for (std::size_t index = 0; index < m_heads.size(); ++index) for (std::size_t index = 0; index < m_heads.size(); ++index)
{ {
auto const &head{m_heads[index]}; auto const &head{m_heads[index]};
if (head.station || false == head.fits || false == head.problems.empty() || (head.shp >= 0.0 && (head.shp_found < 0.0 || std::abs(head.shp_found - head.shp) > kShpTolerance))) if (head.station || false == head.fits || false == head.problems.empty() || (head.shp_of() != shp_state::needless && head.shp_of() != shp_state::placed))
rows.push_back(index); rows.push_back(index);
else else
++fine; ++fine;
@@ -1474,26 +1527,26 @@ void srk_tool::render_heads()
ImGui::TextUnformatted(format(STR_C("Needs: %s"), need_text(need).c_str()).c_str()); ImGui::TextUnformatted(format(STR_C("Needs: %s"), need_text(need).c_str()).c_str());
} }
if (head.shortened > 0) if (head.shortened > 0)
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", format(STR_C("The next signal stands nearer than the braking distance: with it at stop this one slows the trains to %d km/h, Ie-4 § 8 ust. 5"), head.shortened).c_str()); ImGui::TextColored(kDoubtful, "%s", format(STR_C("The next signal stands nearer than the braking distance: with it at stop this one slows the trains to %d km/h, Ie-4 § 8 ust. 5"), head.shortened).c_str());
if (head.indicator) if (head.indicator)
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", STR_C("Give it a W21 indicator \"14\": 140 km/h with the next signal at stop, Ie-4 § 8 ust. 5")); ImGui::TextColored(kDoubtful, "%s", STR_C("Give it a W21 indicator \"14\": 140 km/h with the next signal at stop, Ie-4 § 8 ust. 5"));
if (head.lamps.empty() && head.kind != "warning") if (head.lamps.empty() && head.kind != "warning")
ImGui::TextDisabled("%s", STR_C("A semaphore with arms: its lights aren't checked")); ImGui::TextDisabled("%s", STR_C("A semaphore with arms: its lights aren't checked"));
else if (false == head.fits && head.fit.empty()) else if (false == head.fits && head.fit.empty())
ImGui::TextColored(kWrong, "%s", STR_C("No template of its kind and mount has these lights")); ImGui::TextColored(kWrong, "%s", STR_C("No template of its kind and mount has these lights"));
else if (false == head.fits) else if (false == head.fits)
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", format(STR_C("Its lights don't show all of it: %s does"), head.fit.c_str()).c_str()); ImGui::TextColored(kDoubtful, "%s", format(STR_C("Its lights don't show all of it: %s does"), head.fit.c_str()).c_str());
else if (head.more) else if (head.more)
ImGui::TextDisabled("%s", format(STR_C("It has more lights than it needs: %s will do"), head.fit.c_str()).c_str()); ImGui::TextDisabled("%s", format(STR_C("It has more lights than it needs: %s will do"), head.fit.c_str()).c_str());
for (auto const &problem : head.problems) for (auto const &problem : head.problems)
ImGui::TextColored(kWrong, "%s", problem.c_str()); ImGui::TextColored(kWrong, "%s", problem.c_str());
if (head.shp >= 0.0) if (auto const state{head.shp_of()}; state != shp_state::needless)
{ {
auto const wanted{head.shp > 0.0 ? format(STR_C("its SHP resonator belongs %.0f m before it"), head.shp) : std::string{STR_C("its SHP resonator belongs at it, it tells of the next signal")}}; auto const wanted{head.shp > 0.0 ? format(STR_C("its SHP resonator belongs %.0f m before it"), head.shp) : std::string{STR_C("its SHP resonator belongs at it, it tells of the next signal")}};
if (head.shp_found < 0.0) if (state == shp_state::missing || state == shp_state::beyond)
ImGui::TextColored(kWrong, "%s", (wanted + ", " + (head.shp_short ? STR("the tracks before it end short of it") : STR("none stands"))).c_str()); ImGui::TextColored(kWrong, "%s", (wanted + ", " + (state == shp_state::beyond ? STR("the tracks before it end short of it") : STR("none stands"))).c_str());
else else
ImGui::TextColored(std::abs(head.shp_found - head.shp) > kShpTolerance ? ImVec4(1.0f, 0.75f, 0.3f, 1.0f) : ImVec4(0.45f, 0.85f, 0.45f, 1.0f), "%s", (wanted + ", " + format(STR_C("its event %.0f m before it"), head.shp_found)).c_str()); ImGui::TextColored(state == shp_state::off ? kDoubtful : kRight, "%s", (wanted + ", " + format(STR_C("its event %.0f m before it"), head.shp_found)).c_str());
} }
ImGui::TextDisabled("%s", STR_C("LMB: fly to it")); ImGui::TextDisabled("%s", STR_C("LMB: fly to it"));
ImGui::PopTextWrapPos(); ImGui::PopTextWrapPos();
@@ -1518,25 +1571,25 @@ void srk_tool::render_heads()
if (head.lamps.empty()) if (head.lamps.empty())
ImGui::TextDisabled("-"); ImGui::TextDisabled("-");
else if (false == head.fits) else if (false == head.fits)
ImGui::TextColored(head.fit.empty() ? kWrong : ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", head.fit.empty() ? STR_C("no template") : STR_C("lights missing")); ImGui::TextColored(head.fit.empty() ? kWrong : kDoubtful, "%s", head.fit.empty() ? STR_C("no template") : STR_C("lights missing"));
else if (head.more) else if (head.more)
ImGui::TextDisabled("%s", STR_C("more than needed")); ImGui::TextDisabled("%s", STR_C("more than needed"));
else else
ImGui::TextColored(ImVec4(0.45f, 0.85f, 0.45f, 1.0f), "%s", STR_C("fits")); ImGui::TextColored(kRight, "%s", STR_C("fits"));
if (false == head.problems.empty()) if (false == head.problems.empty())
{ {
ImGui::SameLine(); ImGui::SameLine();
ImGui::TextColored(kWrong, "%s", STR_C("too near")); ImGui::TextColored(kWrong, "%s", STR_C("too near"));
} }
ImGui::TableNextColumn(); ImGui::TableNextColumn();
if (head.shp < 0.0) switch (head.shp_of())
ImGui::TextDisabled("-"); {
else if (head.shp_found < 0.0) case shp_state::needless: ImGui::TextDisabled("-"); break;
ImGui::TextColored(kWrong, "%s", head.shp_short ? STR_C("no track") : STR_C("none")); case shp_state::missing: ImGui::TextColored(kWrong, "%s", STR_C("none")); break;
else if (std::abs(head.shp_found - head.shp) > kShpTolerance) case shp_state::beyond: ImGui::TextColored(kWrong, "%s", STR_C("no track")); break;
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%.0f m", head.shp_found); case shp_state::off: ImGui::TextColored(kDoubtful, "%.0f m", head.shp_found); break;
else default: ImGui::TextColored(kRight, "%s", head.shp > 0.0 ? format("%.0f m", head.shp_found).c_str() : STR_C("at it")); break;
ImGui::TextColored(ImVec4(0.45f, 0.85f, 0.45f, 1.0f), "%s", head.shp > 0.0 ? format("%.0f m", head.shp_found).c_str() : STR_C("at it")); }
} }
ImGui::EndTable(); ImGui::EndTable();
} }
@@ -2274,7 +2327,6 @@ void srk_tool::set_lines()
{ {
m_status.clear(); m_status.clear();
auto const stem{stem_of_open()}; auto const stem{stem_of_open()};
auto const &layout{*m_layout};
std::vector<standing_signal> standing; std::vector<standing_signal> standing;
m_signals.standing_signals(standing, true); m_signals.standing_signals(standing, true);
std::vector<station::line> lines; std::vector<station::line> lines;
@@ -2282,6 +2334,7 @@ void srk_tool::set_lines()
std::vector<std::size_t> entry_standing; std::vector<std::size_t> entry_standing;
if (false == lines_of_open(standing, lines, entries, entry_standing, m_status)) if (false == lines_of_open(standing, lines, entries, entry_standing, m_status))
return; return;
auto const &layout{*m_layout};
// of each line, 1: the automatic block, placed on a double-track line only, 2: the automatic block on a single-track line, its // of each line, 1: the automatic block, placed on a double-track line only, 2: the automatic block on a single-track line, its
// signals facing both ways on a track left as they stand, 0: the semi-automatic one // signals facing both ways on a track left as they stand, 0: the semi-automatic one
std::vector<char> automatic(lines.size(), 0); std::vector<char> automatic(lines.size(), 0);
@@ -2721,6 +2774,8 @@ void srk_tool::survey_signals()
{ {
m_heads.clear(); m_heads.clear();
m_heads_known = false; m_heads_known = false;
m_lines.clear();
m_lines_problem = STR("Looking for the signal templates...");
if (false == m_measured) if (false == m_measured)
measure({}); measure({});
return; return;
@@ -3011,6 +3066,7 @@ void srk_tool::set_shp()
taken.insert(ToLower(event.first)); taken.insert(ToLower(event.first));
struct placing struct placing
{ {
std::string signal; // shown
std::string name; // of the resonator, its event is <name>_shp std::string name; // of the resonator, its event is <name>_shp
TTrack *track{nullptr}; // the trains run into its event on, where a joint of the tracks lies TTrack *track{nullptr}; // the trains run into its event on, where a joint of the tracks lies
TTrack *cut{nullptr}; // the track cut at the point, else TTrack *cut{nullptr}; // the track cut at the point, else
@@ -3023,9 +3079,9 @@ void srk_tool::set_shp()
std::vector<std::string> problems; std::vector<std::string> problems;
for (auto const &head : m_heads) for (auto const &head : m_heads)
{ {
if (head.shp < 0.0 || head.shp_found >= 0.0) if (head.shp_of() != shp_state::missing && head.shp_of() != shp_state::beyond)
continue; continue;
if (head.shp_short) if (head.shp_of() == shp_state::beyond)
{ {
problems.push_back(format(STR_C("%s: the tracks before it end short of %.0f m"), head.name.c_str(), head.shp)); problems.push_back(format(STR_C("%s: the tracks before it end short of %.0f m"), head.name.c_str(), head.shp));
continue; continue;
@@ -3045,29 +3101,8 @@ void srk_tool::set_shp()
problems.push_back(format(STR_C("%s: the tracks before it end short of %.0f m"), head.name.c_str(), head.shp)); problems.push_back(format(STR_C("%s: the tracks before it end short of %.0f m"), head.name.c_str(), head.shp));
continue; continue;
} }
// a joint of the tracks within the tolerance takes the event on the track the trains run into there, else the track is cut
auto best{-1};
auto joint{false}; auto joint{false};
auto miss{kShpTolerance + 1e-6}; auto const best{resonator_at(way, head.shp, joint)};
for (std::size_t k = 0; k + 1 < way.size(); ++k)
if (way[k + 1].track != way[k].track && way[k].track->eType == tt_Normal && std::abs(way[k].distance - head.shp) < miss)
{
miss = std::abs(way[k].distance - head.shp);
best = static_cast<int>(k);
joint = true;
}
if (best < 0)
for (std::size_t k = 0; k < way.size(); ++k)
{
auto const &ends{way[k].track->m_paths.front().points};
if (way[k].track->eType != tt_Normal || std::min(glm::distance(way[k].point, ends[segment_data::point::start]), glm::distance(way[k].point, ends[segment_data::point::end])) <= 0.5)
continue;
if (std::abs(way[k].distance - head.shp) < miss)
{
miss = std::abs(way[k].distance - head.shp);
best = static_cast<int>(k);
}
}
if (best < 0) if (best < 0)
{ {
problems.push_back(format(STR_C("%s: no plain track within %.0f m of the place of its SHP resonator"), head.name.c_str(), kShpTolerance)); problems.push_back(format(STR_C("%s: no plain track within %.0f m of the place of its SHP resonator"), head.name.c_str(), kShpTolerance));
@@ -3090,6 +3125,7 @@ void srk_tool::set_shp()
for (int number = 2; taken.count(ToLower(name) + "_shp") > 0 && number < 100; ++number) for (int number = 2; taken.count(ToLower(name) + "_shp") > 0 && number < 100; ++number)
name = found->name + "_rez" + std::to_string(number); name = found->name + "_rez" + std::to_string(number);
taken.insert(ToLower(name) + "_shp"); taken.insert(ToLower(name) + "_shp");
entry.signal = head.name;
entry.name = name; entry.name = name;
entry.facing = at.facing; entry.facing = at.facing;
entry.point = at.point; entry.point = at.point;
@@ -3132,8 +3168,7 @@ void srk_tool::set_shp()
if (entry.cut == nullptr) if (entry.cut == nullptr)
continue; continue;
auto *track{piece_at(entry.cut, entry.point)}; auto *track{piece_at(entry.cut, entry.point)};
auto const &ends{track->m_paths.front().points}; if (end_gap(*track, entry.point) <= 0.3)
if (std::min(glm::distance(entry.point, ends[segment_data::point::start]), glm::distance(entry.point, ends[segment_data::point::end])) <= 0.3)
continue; continue;
if (captured.insert(track).second) if (captured.insert(track).second)
before.emplace_back(track, editor_track::capture(*track)); before.emplace_back(track, editor_track::capture(*track));
@@ -3152,6 +3187,12 @@ void srk_tool::set_shp()
for (auto const &entry : places) for (auto const &entry : places)
{ {
auto *track{entry.track != nullptr ? entry.track : piece_at(entry.cut, entry.inside)}; auto *track{entry.track != nullptr ? entry.track : piece_at(entry.cut, entry.inside)};
// the trains run into the track at the point: at the start of its path running towards its end
if (glm::distance(track->m_paths.front().points[entry.facing > 0 ? segment_data::point::start : segment_data::point::end], entry.point) > 1.0)
{
problems.push_back(format(STR_C("%s: the track couldn't be cut at the place of its SHP resonator"), entry.signal.c_str()));
continue;
}
if (captured.insert(track).second) if (captured.insert(track).second)
before.emplace_back(track, editor_track::capture(*track)); before.emplace_back(track, editor_track::capture(*track));
editor_track::events(*track, entry.facing > 0 ? 2 : 1).emplace_back(ToLower(entry.name) + "_shp", nullptr); editor_track::events(*track, entry.facing > 0 ? 2 : 1).emplace_back(ToLower(entry.name) + "_shp", nullptr);
@@ -3167,13 +3208,14 @@ void srk_tool::set_shp()
changes.others.push_back(std::move(item)); changes.others.push_back(std::move(item));
} }
std::string error; std::string error;
auto const placed{changes.others.size()};
m_signals.signal_batch(changes, std::move(before), std::move(created), error); m_signals.signal_batch(changes, std::move(before), std::move(created), error);
if (false == error.empty()) if (false == error.empty())
{ {
m_status = format(STR_C("The SHP resonators couldn't be placed: %s"), error.c_str()); m_status = format(STR_C("The SHP resonators couldn't be placed: %s. Ctrl+Z takes back the tracks cut"), error.c_str());
return; return;
} }
m_status = format(STR_P("%zu SHP resonator placed", "%zu SHP resonators placed", places.size()), places.size()) + ", " + format(STR_P("%zu track cut", "%zu tracks cut", cuts), cuts) + ". " + STR("Ctrl+Z takes it back, the resonators work after the scenery is saved and opened again"); m_status = format(STR_P("%zu SHP resonator placed", "%zu SHP resonators placed", placed), placed) + ", " + format(STR_P("%zu track cut", "%zu tracks cut", cuts), cuts) + ". " + STR("Ctrl+Z takes it back, the resonators work after the scenery is saved and opened again");
for (auto const &problem : problems) for (auto const &problem : problems)
m_status += "\n" + problem; m_status += "\n" + problem;
} }

View File

@@ -125,6 +125,15 @@ class srk_tool final : public editor_tool
int m_length_hover{-1}; // of the lengths, the row under the mouse int m_length_hover{-1}; // of the lengths, the row under the mouse
int m_length_canvas{-1}; // of the lengths, of the track under the mouse in the schematic int m_length_canvas{-1}; // of the lengths, of the track under the mouse in the schematic
int m_length_shown{-1}; // of the lengths, the one drawn: of the row or the track under the mouse, else of the track selected int m_length_shown{-1}; // of the lengths, the one drawn: of the row or the track under the mouse, else of the track selected
// how the SHP track resonator of a signal stands
enum class shp_state
{
needless, // the signal needs none
missing,
beyond, // missing, the tracks before the signal end short of its place
off, // farther from its place than allowed
placed
};
// a signal of the station or of its lines, its lights as the routes of its trains need them: Ie-1, Ie-4 § 8 // a signal of the station or of its lines, its lights as the routes of its trains need them: Ie-1, Ie-4 § 8
struct head_check struct head_check
{ {
@@ -153,6 +162,7 @@ class srk_tool final : public editor_tool
double shp{-1.0}; double shp{-1.0};
double shp_found{-1.0}; // m before it the trains run into the event of the SHP resonator standing, -1 for none double shp_found{-1.0}; // m before it the trains run into the event of the SHP resonator standing, -1 for none
bool shp_short{false}; // the tracks before it end short of the place of its resonator bool shp_short{false}; // the tracks before it end short of the place of its resonator
shp_state shp_of() const;
}; };
std::vector<head_check> m_heads; std::vector<head_check> m_heads;
bool m_heads_known{false}; // the templates found bool m_heads_known{false}; // the templates found

View File

@@ -11233,8 +11233,11 @@ msgstr[2] "przecięto %zu torów"
msgid "Ctrl+Z takes it back, the resonators work after the scenery is saved and opened again" msgid "Ctrl+Z takes it back, the resonators work after the scenery is saved and opened again"
msgstr "Ctrl+Z cofa, rezonatory działają po zapisaniu i ponownym otwarciu scenerii" msgstr "Ctrl+Z cofa, rezonatory działają po zapisaniu i ponownym otwarciu scenerii"
msgid "The SHP resonators couldn't be placed: %s" msgid "The SHP resonators couldn't be placed: %s. Ctrl+Z takes back the tracks cut"
msgstr "Nie udało się postawić rezonatorów SHP: %s" msgstr "Nie udało się postawić rezonatorów SHP: %s. Ctrl+Z cofa przecięcie torów"
msgid "%s: the track couldn't be cut at the place of its SHP resonator"
msgstr "%s: nie udało się przeciąć toru w miejscu rezonatora SHP"
msgid "at it" msgid "at it"
msgstr "przy nim" msgstr "przy nim"