diff --git a/application/editormode.h b/application/editormode.h index 0acdf196..518faca5 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -402,7 +402,9 @@ class editor_mode : public application_mode, private editor_track::observer // Rebuild: the chunks there already are made anew too, and the formation laid before is forgotten, as when the tracks moved void nmt_make(std::vector const &Keys, bool const Rebuild = false); bool add_chunk_from_nmt(heightmap::chunk_key const &Key); - void nmt_shape_tracks(); + // Only: the paths of these tracks are shaped anew, the modifiers of those removed taken away; null: all of them. the number + // of the modifiers laid + std::size_t nmt_shape_tracks(std::vector const *Only = nullptr); void nmt_update(); void render_nmt_ui(); // a scenery begun from nothing, with no tracks nor terrain, is asked as the first track is laid whether it wants the terrain @@ -2275,6 +2277,9 @@ class editor_mode : public application_mode, private editor_track::observer std::vector samples; std::vector created; // the paths the last apply split off the route std::vector terrain; // NaN where there's no terrain + std::vector model; // the ground of the terrain model (NMT) as measured; NaN where it's not known + bool model_waiting{false}; // squares of the model under the route still to come + double model_polled{0.0}; // what the joined ends take their height and grade from, when it's a switch: shown on the plot std::string start_inherits, end_inherits; // depth of the formation under the grade line, the rail top, of the track of each sample: the formation, not the rails, @@ -2306,7 +2311,13 @@ class editor_mode : public application_mode, private editor_track::observer double view_centre{0.0}; float exaggeration{20.0f}; bool show_terrain{true}; + bool show_model{true}; bool show_track{true}; + bool camera_follows{true}; + // the part of the route the plot shows: as last seen and when, and as the camera last showed it + std::pair view_seen{0.0, 0.0}; + double view_seen_time{0.0}; + std::pair view_framed{0.0, 0.0}; bool show_plan{true}; int selected{-1}; int dragging{-1}; @@ -2368,6 +2379,8 @@ class editor_mode : public application_mode, private editor_track::observer void profile_open_selection(std::vector const &Tracks); void profile_follow_selection(); void profile_resample(); + // the ground of the terrain model under the route, as it was measured before any earthworks + void profile_sample_model(); void profile_recognize(); void profile_fit_ground(); // depth of the foot of the ballast under the rail top, and its distance from the axis @@ -2379,8 +2392,14 @@ class editor_mode : public application_mode, private editor_track::observer void profile_restore_ground(); void render_profile_earthworks(); void profile_check(); - void profile_apply(); + // Terrain: the heightmap terrain follows the tracks changed + void profile_apply(bool const Terrain = false); + // the terrain under the tracks changed by the last Edits steps of the history which changed tracks + void profile_follow_terrain(std::size_t const Edits); void profile_fit_view(); + // the camera shows the part of the route the plot shows, once the plot stops moving + void profile_follow_view(); + void profile_frame_view(); void profile_after_undo(); void profile_follow_edits(); struct profile_view; diff --git a/application/editormodenmt.cpp b/application/editormodenmt.cpp index 26b6feab..5a01a914 100644 --- a/application/editormodenmt.cpp +++ b/application/editormodenmt.cpp @@ -508,11 +508,12 @@ bool editor_mode::add_chunk_from_nmt(heightmap::chunk_key const &Key) return true; } -void editor_mode::nmt_shape_tracks() +std::size_t editor_mode::nmt_shape_tracks(std::vector const *Only) { auto &state{m_nmt_state}; if (false == ensure_terrain()) - return; + return 0; + auto const wanted = [&](TTrack const *Track) { return Only == nullptr || std::find(Only->begin(), Only->end(), Track) != Only->end(); }; // one modifier per path: the formation under its axis and the slopes from its edges to the ground. laid over each other the // modifiers keep the highest of the slopes going down and the lowest of those going up, so tracks side by side share one // formation instead of a saw of their own ones @@ -524,6 +525,8 @@ void editor_mode::nmt_shape_tracks() bool broken{false}; for (auto const &place : nmt_track_places(step)) { + if (false == wanted(place.track)) + continue; std::pair const path{place.track, place.path}; if (path != previous) broken = false; @@ -552,9 +555,28 @@ void editor_mode::nmt_shape_tracks() for (auto &[path, modifier] : modifiers) if (modifier.points.size() >= 2) laid.push_back(std::move(modifier)); + if (Only != nullptr) + { + // the modifiers the tracks don't have any more: of the pieces of a path gone, of the tracks removed + std::vector gone; + for (auto const *track : *Only) + { + if (track == nullptr || track->name().empty()) + continue; + auto const own{"nmt " + track->name()}; + for (auto const &modifier : m_streamer.modifiers()) + if ((modifier.name == own || modifier.name.starts_with(own + " ")) && std::none_of(laid.begin(), laid.end(), [&](heightmap::modifier const &Laid) { return Laid.name == modifier.name; })) + gone.push_back(modifier.name); + } + for (auto const &name : gone) + m_streamer.remove_modifier(name); + } + auto const count{laid.size()}; m_streamer.modifiers(std::move(laid)); forget_ground(); - state.shaped = modifiers.size(); + if (Only == nullptr) + state.shaped = modifiers.size(); + return count; } void editor_mode::nmt_update() diff --git a/application/editormodeprofile.cpp b/application/editormodeprofile.cpp index fafb1524..c49b12b7 100644 --- a/application/editormodeprofile.cpp +++ b/application/editormodeprofile.cpp @@ -22,6 +22,7 @@ http://mozilla.org/MPL/2.0/. #include "scene/scenelayers.h" #include "simulation/simulation.h" #include "utilities/Globals.h" +#include "utilities/glmHelpers.h" #include "world/TrackSleepers.h" #include "utilities/Logs.h" #include "utilities/translation.h" @@ -46,6 +47,7 @@ ImU32 const kTangents{IM_COL32(255, 210, 60, 130)}; ImU32 const kTrack{IM_COL32(190, 190, 190, 255)}; ImU32 const kTerrain{IM_COL32(170, 120, 70, 255)}; ImU32 const kFormation{IM_COL32(215, 185, 140, 200)}; +ImU32 const kModel{IM_COL32(70, 200, 190, 230)}; // the ground of the terrain model, as measured double constexpr kNoBed{0.05}; // m, a trackbed shallower is none // a design the track lay on when it was kept, within kApplied, was applied; where the track parts from it by more than kStale since, // it was changed by other tools, and the line is read from the track there, over the stretch and kStaleMargin around it @@ -730,6 +732,8 @@ void editor_mode::profile_take(editor_track::route Route, TTrack *From, TTrack * } } profile_fit_view(); + // the camera stays where the route was picked, it follows the plot once that's moved + state.view_seen = state.view_framed = {state.view_from, state.view_to}; } void editor_mode::profile_resample() @@ -747,6 +751,7 @@ void editor_mode::profile_resample() for (std::size_t i = 0; i < samples.size(); ++i) if (found[i] != 0) state.terrain[i] = heights[i]; + profile_sample_model(); state.beds.assign(samples.size(), 0.0); std::vector bedless(samples.size(), 0); std::vector own; @@ -837,6 +842,52 @@ void editor_mode::profile_resample() profile_check(); } +void editor_mode::profile_sample_model() +{ + auto &state{m_profile}; + auto const &samples{state.samples}; + state.model.assign(samples.size(), std::numeric_limits::quiet_NaN()); + state.model_waiting = false; + state.model_polled = ImGui::GetTime(); + if (samples.empty() || false == state.show_model) + return; + nmt_locate(); + if (false == m_nmt.placed()) + return; + // the squares along the route, not all those under the rectangle around it: a long line crosses the grid + std::size_t constexpr run{200}; + for (std::size_t first = 0; first < samples.size(); first += run) + { + glm::dvec2 low{samples[first].position.x, samples[first].position.z}; + auto high{low}; + for (auto i = first; i < std::min(samples.size(), first + run + 1); ++i) + { + low = glm::min(low, glm::dvec2{samples[i].position.x, samples[i].position.z}); + high = glm::max(high, glm::dvec2{samples[i].position.x, samples[i].position.z}); + } + if (false == m_nmt.request(low - glm::dvec2{2.0}, high + glm::dvec2{2.0})) + state.model_waiting = true; + } + std::vector differences; + for (std::size_t i = 0; i < samples.size(); ++i) + if (double height; m_nmt.height(samples[i].position.x, samples[i].position.z, height)) + { + state.model[i] = height; + differences.push_back(samples[i].position.y - height); + } + // a scenery put onto the model lies at its heights, or as far from them as the model was found to lie from it; one built at + // heights of its own, never put onto it, is compared by the shape: the model goes as far as the route lies from it + auto offset{m_nmt_state.offset}; + if (false == m_nmt_state.fitted && false == differences.empty()) + { + std::nth_element(differences.begin(), differences.begin() + differences.size() / 2, differences.end()); + if (auto const median{differences[differences.size() / 2]}; std::abs(median) > 30.0) + offset = median; + } + for (auto &height : state.model) + height += offset; +} + void editor_mode::profile_recognize() { auto &state{m_profile}; @@ -1306,13 +1357,101 @@ void editor_mode::profile_fit_view() state.exaggeration = 0.0f; } -void editor_mode::profile_apply() +void editor_mode::profile_follow_view() +{ + auto &state{m_profile}; + std::pair const shown{state.view_from, state.view_to}; + auto const now{ImGui::GetTime()}; + if (shown != state.view_seen) + { + state.view_seen = shown; + state.view_seen_time = now; + return; + } + if (false == state.camera_follows || shown == state.view_framed || now - state.view_seen_time < 0.25) + return; + state.view_framed = shown; + profile_frame_view(); +} + +void editor_mode::profile_frame_view() +{ + auto const &state{m_profile}; + std::vector points; + for (auto const &sample : state.samples) + if (sample.chainage >= state.view_from && sample.chainage <= state.view_to) + points.push_back(sample.position); + auto const &io{ImGui::GetIO()}; + if (points.size() < 2 || io.DisplaySize.y <= 0.0f) + return; + glm::dvec3 low{points.front()}; + glm::dvec3 high{points.front()}; + for (auto const &point : points) + { + low = glm::min(low, point); + high = glm::max(high, point); + } + auto const centre{(low + high) * 0.5}; + // the room the docks and the strip of the plot leave to the view, and how far its middle lies from the middle of the screen, + // in pixels + glm::dvec2 const screen{io.DisplaySize.x, io.DisplaySize.y}; + auto const viewlow{editor_ui::view_min()}; + auto viewhigh{editor_ui::view_max()}; + if (state.strip_top > viewlow.y + 100.0f) + viewhigh.y = std::min(viewhigh.y, state.strip_top); + auto const width{std::max(50.0, static_cast(viewhigh.x - viewlow.x))}; + auto const height{std::max(50.0, static_cast(viewhigh.y - viewlow.y))}; + glm::dvec2 const shift{(viewlow.x + viewhigh.x) * 0.5 - screen.x * 0.5, (viewlow.y + viewhigh.y) * 0.5 - screen.y * 0.5}; + double constexpr margin{1.1}; + if (Global.EditorOrtho) + { + auto const yaw{static_cast(Camera.Angle.y)}; + double extent{5.0}; + for (auto const &point : points) + { + auto const local{RotateY(point - centre, -yaw)}; + extent = std::max({extent, std::abs(local.x) * margin * screen.y / width, std::abs(local.z) * margin * screen.y / height}); + } + Global.EditorOrthoExtent = std::clamp(static_cast(extent), 5.0f, 50000.0f); + auto const scale{2.0 * static_cast(Global.EditorOrthoExtent) / screen.y}; + auto const offset{RotateY(glm::dvec3{shift.x * scale, 0.0, shift.y * scale}, yaw)}; + Camera.Pos.x = centre.x - offset.x; + Camera.Pos.z = centre.z - offset.z; + Camera.Velocity = {}; + m_focus_active = false; + Global.pCamera = Camera; + return; + } + // seen from the side, as the plot shows it: the start on the left, from a little above + glm::dvec3 chord{points.back().x - points.front().x, 0.0, points.back().z - points.front().z}; + chord = glm::length(chord) > 1e-6 ? glm::normalize(chord) : glm::dvec3{1.0, 0.0, 0.0}; + glm::dvec3 const up{0.0, 1.0, 0.0}; + auto const look{glm::normalize(glm::cross(up, chord) + glm::dvec3{0.0, -0.4, 0.0})}; + auto const right{glm::normalize(glm::cross(look, up))}; + auto const above{glm::cross(right, look)}; + auto const tangent{std::tan(glm::radians(static_cast(Global.FieldOfView) / std::max(0.01, static_cast(Global.ZoomFactor))) * 0.5)}; + auto const across{tangent * width / screen.y}; + auto const upward{tangent * height / screen.y}; + double distance{10.0}; + for (auto const &point : points) + { + auto const offset{point - centre}; + auto const depth{glm::dot(offset, look)}; + distance = std::max({distance, std::abs(glm::dot(offset, right)) * margin / across - depth, std::abs(glm::dot(offset, above)) * margin / upward - depth}); + } + distance = std::min(distance, 8000.0); + auto const pixel{2.0 * distance * tangent / screen.y}; + fly_to(centre - right * (shift.x * pixel) + above * (shift.y * pixel), look, distance, 0.35); +} + +void editor_mode::profile_apply(bool const Terrain) { auto &state{m_profile}; state.status.clear(); state.error.clear(); if (state.route.spans.empty() || state.line.points.size() < 2) return; + auto const edits{m_track_edits}; if (has_errors(state.issues)) { state.error = STR_C("Correct the errors to apply the grade line"); @@ -1370,9 +1509,65 @@ void editor_mode::profile_apply() auto const followed{follow_crossovers(before)}; if (followed > 0) state.status += format(STR_C("\nThe tracks across %zu crossover(s) followed the change"), followed); + if (Terrain) + profile_follow_terrain(static_cast(m_track_edits - edits)); WriteLog("Editor: vertical profile - " + state.status, logtype::generic); } +void editor_mode::profile_follow_terrain(std::size_t const Edits) +{ + auto &state{m_profile}; + // the heightmap terrain the scenery has; the ground of a scenery without it stays as it is + if (false == m_streamer.active() || Edits == 0) + return; + std::vector tracks; + std::size_t found{0}; + for (auto step{m_history.rbegin()}; step != m_history.rend() && found < Edits; ++step) + { + if (step->action != EditorSnapshot::Action::TrackEdit) + continue; + ++found; + for (auto const &entry : step->tracks) + tracks.push_back(entry.first); + tracks.insert(tracks.end(), step->created.begin(), step->created.end()); + tracks.insert(tracks.end(), step->removed.begin(), step->removed.end()); + } + std::erase(tracks, nullptr); + std::sort(tracks.begin(), tracks.end()); + tracks.erase(std::unique(tracks.begin(), tracks.end()), tracks.end()); + if (tracks.empty()) + return; + // the route shaped by the earthworks of the profile keeps its modifier, shaped again to the new grade line; the tracks + // shaped one by one, as the terrain model lays them, get their formations anew + auto const name = [](TTrack const *Track) { return Track != nullptr && false == Track->name().empty() ? Track->name() : std::string{"?"}; }; + auto const route{"route " + name(state.from) + " - " + name(state.to)}; + auto const &modifiers{m_streamer.modifiers()}; + auto const has = [&](std::string const &Name) { return std::any_of(modifiers.begin(), modifiers.end(), [&](heightmap::modifier const &Modifier) { return Modifier.name == Name; }); }; + auto const shaped{has(route) || (false == state.earthworks.modifier.empty() && has(state.earthworks.modifier))}; + auto const own{std::any_of(tracks.begin(), tracks.end(), [&](TTrack const *Track) { + return false == Track->name().empty() && std::any_of(modifiers.begin(), modifiers.end(), [&](heightmap::modifier const &Modifier) { return Modifier.name == "nmt " + Track->name() || Modifier.name.starts_with("nmt " + Track->name() + " "); }); + })}; + auto status{state.status}; + std::size_t laid{0}; + if (false == shaped || own) + laid = nmt_shape_tracks(&tracks); + if (shaped) + { + profile_shape_ground(); + status += "\n" + state.status; + } + else + profile_resample(); + if (laid > 0) + status += format(STR_P("\nThe terrain followed the tracks: %zu formation laid anew", "\nThe terrain followed the tracks: %zu formations laid anew", laid), laid); + state.status = status; + // the terrain goes back with the grade line in one step + auto const before{m_history.empty() ? EditorSnapshot::Action::Other : m_history.back().action}; + terrain_history_step(); + if (before != EditorSnapshot::Action::Terrain && false == m_history.empty() && m_history.back().action == EditorSnapshot::Action::Terrain) + m_history.back().joined = true; +} + void editor_mode::profile_after_undo() { auto &state{m_profile}; @@ -1576,7 +1771,7 @@ void editor_mode::render_profile_header() ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.55f, 0.25f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.68f, 0.3f, 1.0f)); if (ImGui::Button((label + "###profileapply").c_str())) - profile_apply(); + profile_apply(true); ImGui::PopStyleColor(2); ImGui::EndDisabled(); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) @@ -1974,15 +2169,31 @@ struct editor_mode::profile_view void editor_mode::render_profile_toolbar() { auto &state{m_profile}; + // the next item on the same line while it fits, past the edge of the plot on the next one + auto const edge{ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x}; + auto const next = [&](float const Gap, float const Width) { + ImGui::SameLine(0.0f, Gap); + if (ImGui::GetCursorScreenPos().x + Width > edge) + ImGui::NewLine(); + }; + auto const pill = [](char const *Label) { return ImGui::CalcTextSize(Label).x + ImGui::GetFrameHeight() * 1.75f; }; ImGui::AlignTextToFramePadding(); ImGui::TextDisabled("%s", STR_C("Show:")); - ImGui::SameLine(); + next(ImGui::GetStyle().ItemSpacing.x, pill(STR_C("Track"))); pill_toggle(STR_C("Track"), kTrack, state.show_track); - ImGui::SameLine(0.0f, 4.0f); + next(4.0f, pill(STR_C("Terrain"))); pill_toggle(STR_C("Terrain"), kTerrain, state.show_terrain); - ImGui::SameLine(0.0f, 4.0f); + if (m_nmt.placed()) + { + next(4.0f, pill(STR_C("Terrain model"))); + if (pill_toggle(STR_C("Terrain model"), kModel, state.show_model)) + profile_sample_model(); + item_tooltip(state.model_waiting ? "The ground of the terrain model (NMT) as it was measured, before the earthworks; its squares along the route are still being fetched" + : "The ground of the terrain model (NMT) as it was measured, before the earthworks"); + } + next(4.0f, pill(STR_C("Plan curvature"))); pill_toggle(STR_C("Plan curvature"), kPlan, state.show_plan); - ImGui::SameLine(0.0f, 6.0f); + next(6.0f, ImGui::CalcTextSize("(?)").x); ImGui::TextDisabled("(?)"); if (ImGui::IsItemHovered()) { @@ -2003,6 +2214,7 @@ void editor_mode::render_profile_toolbar() entry(kTrack, "Track"); entry(kTerrain, "Terrain"); entry(kFormation, "Formation"); + entry(kModel, "Terrain model"); entry(kPlan, "Plan curvature"); entry(kSwitchLabel, "Switches"); entry(kSharedEdge, "Shared timbers"); @@ -2010,16 +2222,20 @@ void editor_mode::render_profile_toolbar() entry(kWarning, "Warnings"); ImGui::EndTooltip(); } - ImGui::SameLine(0.0f, 14.0f); + next(14.0f, 120.0f); ImGui::SetNextItemWidth(120.0f); ImGui::SliderFloat("##exaggeration", &state.exaggeration, 1.0f, 500.0f, STR_C("heights x%.0f"), ImGuiSliderFlags_Logarithmic); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", STR_C("Exaggeration of the heights against the lengths, also Ctrl+wheel over the plot")); - ImGui::SameLine(); + next(ImGui::GetStyle().ItemSpacing.x, ImGui::CalcTextSize(STR_C("Fit")).x + ImGui::GetStyle().FramePadding.x * 2.0f); if (ImGui::Button(STR_C("Fit"))) profile_fit_view(); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", STR_C("Shows the whole route")); + next(14.0f, pill(STR_C("Camera follows"))); + if (pill_toggle(STR_C("Camera follows"), kGradeLine, state.camera_follows) && state.camera_follows) + state.view_framed = {}; + item_tooltip("The view shows the part of the route the plot shows, once the plot is zoomed or moved"); } void editor_mode::render_profile_canvas(float const Height) @@ -2035,7 +2251,10 @@ void editor_mode::render_profile_canvas(float const Height) if (state.exaggeration <= 0.0f) profile_fit_exaggeration(profile_view{state, corner, size}); + if (state.model_waiting && ImGui::GetTime() - state.model_polled > 0.5) + profile_sample_model(); profile_navigate(profile_view{state, corner, size}, hovered); + profile_follow_view(); profile_view const view{state, corner, size}; if (profile_canvas_edit(view, hovered)) profile_edited(); @@ -2415,6 +2634,8 @@ void editor_mode::draw_profile_canvas(profile_view const &View, ImDrawList &Draw // the formation under the grade line, which is what meets the ground polyline([&](std::size_t const I) { return profile::elevation(state.line, samples[I].chainage) - (I < state.beds.size() ? state.beds[I] : 0.0); }, kFormation, 1.0f); } + if (state.show_model && state.model.size() == samples.size()) + polyline([&](std::size_t const I) { return state.model[I]; }, kModel, 1.5f); if (state.show_track) polyline([&](std::size_t const I) { return samples[I].position.y; }, kTrack, 1.5f); @@ -2530,6 +2751,8 @@ void editor_mode::profile_canvas_tooltip() const ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(kFormation), STR_C("Formation %.3f m, %.2f m under the rail top"), formation, designed - formation); ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(kTerrain), STR_C("Terrain %.3f m, %s %.2f m"), state.terrain[index], formation > state.terrain[index] ? STR_C("fill") : STR_C("cut"), std::abs(formation - state.terrain[index])); } + if (state.show_model && index < state.model.size() && false == std::isnan(state.model[index])) + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(kModel), STR_C("Terrain model %.3f m, the grade line %+.2f m from it"), state.model[index], designed - state.model[index]); auto const &span{state.route.spans[samples[index].span]}; ImGui::TextDisabled("%s%s", span.track->name().c_str(), span.track->eType != tt_Normal ? STR_C(" (switch, tilted as a whole with its branch)") : ""); ImGui::EndTooltip(); diff --git a/application/editormodeselftesttrack.cpp b/application/editormodeselftesttrack.cpp index 2ecf86ea..0c8ca5b5 100644 --- a/application/editormodeselftesttrack.cpp +++ b/application/editormodeselftesttrack.cpp @@ -330,6 +330,24 @@ editor_mode::selftest_result editor_mode::selftest_track(std::string const &comm } return selftest_result::handled; } + if (command == "vprofilemodel") + { + // the ground of the terrain model along the open route, and where the camera is + auto const &state{m_profile}; + std::size_t known{0}; + double low{std::numeric_limits::max()}; + double high{std::numeric_limits::lowest()}; + for (std::size_t i = 0; i < state.model.size() && i < state.samples.size(); ++i) + if (false == std::isnan(state.model[i])) + { + ++known; + low = std::min(low, state.samples[i].position.y - state.model[i]); + high = std::max(high, state.samples[i].position.y - state.model[i]); + } + WriteLog(format("SELFTEST vprofilemodel: %zu of %zu samples known%s, the track %.2f..%.2f m from the model; view %.1f..%.1f, camera %.1f %.1f %.1f angle %.3f %.3f, ortho %d extent %.0f", known, state.samples.size(), state.model_waiting ? " (waiting)" : "", known > 0 ? low : 0.0, known > 0 ? high : 0.0, + state.view_from, state.view_to, Camera.Pos.x, Camera.Pos.y, Camera.Pos.z, Camera.Angle.x, Camera.Angle.y, Global.EditorOrtho ? 1 : 0, Global.EditorOrthoExtent)); + return selftest_result::handled; + } if (command == "vprofilezoom") { // the plot of the open profile from one chainage to another, the heights exaggerated as given (0: fitted) @@ -382,7 +400,8 @@ editor_mode::selftest_result editor_mode::selftest_track(std::string const &comm { // a plain grade line from the height of the start of the open route, applied to it double grade{0.0}; - words >> grade; + std::string option; + words >> grade >> option; auto &state{m_profile}; if (state.samples.empty()) { @@ -394,7 +413,7 @@ editor_mode::selftest_result editor_mode::selftest_track(std::string const &comm state.line.join_start = false; state.line.join_end = false; profile_check(); - profile_apply(); + profile_apply(option == "terrain"); WriteLog("SELFTEST vprofileapply: " + state.status + " " + state.error); for (auto const &span : state.route.spans) { diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 8943ca6c..4d64ab93 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -9865,3 +9865,27 @@ msgstr "Nie dało się przeciąć \"%s\", żeby zmienić pasy" msgid "Looking for the templates..." msgstr "Szukam szablonów..." + +msgid "Terrain model" +msgstr "Teren z NMT" + +msgid "The ground of the terrain model (NMT) as it was measured, before the earthworks; its squares along the route are still being fetched" +msgstr "Teren z modelu NMT taki, jak go zmierzono, sprzed robót ziemnych; jego kwadraty wzdłuż trasy jeszcze się pobierają" + +msgid "The ground of the terrain model (NMT) as it was measured, before the earthworks" +msgstr "Teren z modelu NMT taki, jak go zmierzono, sprzed robót ziemnych" + +msgid "Terrain model %.3f m, the grade line %+.2f m from it" +msgstr "Teren z NMT %.3f m, niweleta %+.2f m od niego" + +msgid "Camera follows" +msgstr "Kamera za wykresem" + +msgid "The view shows the part of the route the plot shows, once the plot is zoomed or moved" +msgstr "Widok pokazuje ten kawałek trasy, który widać na wykresie, kiedy go przybliżysz albo przesuniesz" + +msgid "\nThe terrain followed the tracks: %zu formation laid anew" +msgid_plural "\nThe terrain followed the tracks: %zu formations laid anew" +msgstr[0] "\nTeren poszedł za torami: %zu torowisko ułożone od nowa" +msgstr[1] "\nTeren poszedł za torami: %zu torowiska ułożone od nowa" +msgstr[2] "\nTeren poszedł za torami: %zu torowisk ułożonych od nowa"