From e86a536a9d0c0b529215af35f4086d24b103999a Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 21 Oct 2026 17:25:17 +0200 Subject: [PATCH] fix(editor): a point taken out of a grade line leaves its neighbours with the radius of the limits, and paths meeting at a sharp break keep their own grades The points of a grade line read from the tracks keep the radius measured there; once a point between them was taken out, the breaks of its neighbours changed while their radii stayed, so a break without a curve read as 0.4 per mille became one of 9 per mille with no curve, and the tracks took it as a kink. The neighbours of the point taken out follow the limits now, with the button, Del and the self-test alike (profile_remove_point). Applying a line with a break without a curve gave the path which ends at the break the grade of the element after it, so its Hermite heights bowed 0.12 m off the line over 100 m before the break; each path takes the grade on its own side now. Self-test commands vprofilerun, vprofiledel and vprofileput. --- application/editormode.h | 2 + application/editormodeprofile.cpp | 25 +++++++---- application/editormodeselftesttrack.cpp | 55 +++++++++++++++++++++++++ editor/editorTrack.cpp | 6 ++- 4 files changed, 78 insertions(+), 10 deletions(-) diff --git a/application/editormode.h b/application/editormode.h index da6b1501..10d20e32 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -2179,6 +2179,8 @@ class editor_mode : public application_mode, private editor_track::observer, pri void profile_forget(); void profile_open_stored(std::size_t const Index); void profile_edited(); + // the radii of its neighbours were for the breaks of the grade it made, they follow the limits after it + void profile_remove_point(int const Index); void render_profile_body(); void render_profile_strip(); void render_profile_header(); diff --git a/application/editormodeprofile.cpp b/application/editormodeprofile.cpp index 75df8b70..24e495f6 100644 --- a/application/editormodeprofile.cpp +++ b/application/editormodeprofile.cpp @@ -630,6 +630,20 @@ void editor_mode::profile_edited() first_step_done(first_step::profile_point); } +void editor_mode::profile_remove_point(int const Index) +{ + auto &state{m_profile}; + auto &points{state.line.points}; + if (Index <= 0 || Index + 1 >= static_cast(points.size())) + return; + points.erase(points.begin() + Index); + for (auto const neighbour : {Index - 1, Index}) + if (neighbour > 0 && neighbour + 1 < static_cast(points.size()) && false == points[neighbour].joint) + points[neighbour].automatic = true; + state.selected = -1; + profile_edited(); +} + void editor_mode::profile_open(TTrack *From, TTrack *To) { auto &state{m_profile}; @@ -2086,9 +2100,8 @@ void editor_mode::render_profile_point() ImGui::SameLine(); if (ImGui::Button(STR_C("Remove Del"))) { - points.erase(points.begin() + index); - state.selected = -1; - changed = true; + profile_remove_point(index); + return; } } } @@ -2513,11 +2526,7 @@ bool editor_mode::profile_canvas_edit(profile_view const &View, bool const Hover else state.curve_grip = -1; if ((Hovered || ImGui::IsWindowFocused()) && state.selected > 0 && state.selected + 1 < static_cast(points.size()) && false == points[state.selected].joint && ImGui::IsKeyPressed(ImGuiKey_Delete)) - { - points.erase(points.begin() + state.selected); - state.selected = -1; - changed = true; - } + profile_remove_point(state.selected); return changed; } diff --git a/application/editormodeselftesttrack.cpp b/application/editormodeselftesttrack.cpp index c1182d20..9068b20a 100644 --- a/application/editormodeselftesttrack.cpp +++ b/application/editormodeselftesttrack.cpp @@ -396,6 +396,61 @@ editor_mode::selftest_result editor_mode::selftest_track(std::string const &comm WriteLog(format("SELFTEST vprofile zone %.3f %.3f %s", zone.from, zone.to, zone.name.c_str())); return selftest_result::handled; } + if (command == "vprofilerun" || command == "vprofiledel" || command == "vprofileput") + { + // the grade line of the line through a track, a point of it taken out as with Del, the line applied + auto &state{m_profile}; + if (command == "vprofilerun") + { + std::string name; + double length{std::numeric_limits::infinity()}; + words >> name >> length; + auto *track{simulation::Paths.find(name)}; + if (track == nullptr) + { + WriteLog("SELFTEST vprofilerun: no track " + name); + return selftest_result::handled; + } + state.run_length = length; + profile_open_run(*track); + } + else if (command == "vprofiledel") + { + int index{-1}; + words >> index; + auto &points{state.line.points}; + if (index <= 0 || index + 1 >= static_cast(points.size()) || points[index].joint) + { + WriteLog(format("SELFTEST vprofiledel: point %d can't be taken out", index)); + return selftest_result::handled; + } + profile_remove_point(index); + } + else + { + std::string option; + words >> option; + profile_apply(option == "terrain"); + WriteLog("SELFTEST vprofileput: " + state.status + " " + state.error); + } + std::string points; + for (auto const &point : state.line.points) + points += format(" (%.1f %.3f R%.0f%s%s)", point.chainage, point.elevation, point.radius, point.automatic ? "" : " fixed", point.joint ? " joint" : ""); + std::string issues; + for (auto const &issue : state.issues) + issues += std::string{issue.error ? " | error: " : " | "} + issue.text; + double low{0.0}, high{0.0}, at_low{0.0}, at_high{0.0}; + for (auto const &sample : state.samples) + { + auto const off{sample.position.y - profile::elevation(state.line, sample.chainage)}; + if (off < low) + low = off, at_low = sample.chainage; + if (off > high) + high = off, at_high = sample.chainage; + } + WriteLog(format("SELFTEST %s: route %.1f m, %zu samples, track against the line %.3f at %.1f .. %+.3f at %.1f; points%s%s", command.c_str(), state.route.length, state.samples.size(), low, at_low, high, at_high, points.c_str(), issues.c_str())); + return selftest_result::handled; + } if (command == "vprofileapply") { // a plain grade line from the height of the start of the open route, applied to it diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index a10beb53..ba5a65be 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -2559,7 +2559,9 @@ std::vector editor_track::apply_profile(route &Route, auto const endchainage{span.forward ? span.to : span.from}; auto const sign{span.forward ? 1.0 : -1.0}; auto &path{span.track->m_paths[span.path]}; - set_heights(path, Elevation(startchainage), Elevation(endchainage), Grade(startchainage) * sign, Grade(endchainage) * sign); + // at a break of the grade without a curve each path takes the grade on its own side of it + auto const inward = [&](double const Chainage, double const Other) { return Grade(Chainage + std::min(1e-3, std::abs(Other - Chainage) * 0.5) * (Other > Chainage ? 1.0 : -1.0)); }; + set_heights(path, Elevation(startchainage), Elevation(endchainage), inward(startchainage, endchainage) * sign, inward(endchainage, startchainage) * sign); // the pieces of a path made of several curves follow the grade line along them, else the path would part from them and lose them std::vector lengths; double total{0.0}; @@ -2576,7 +2578,7 @@ std::vector editor_track::apply_profile(route &Route, { auto const first{at(along)}; auto const last{at(along + lengths[k])}; - set_heights(path.pieces[k], Elevation(first), Elevation(last), Grade(first) * sign, Grade(last) * sign); + set_heights(path.pieces[k], Elevation(first), Elevation(last), inward(first, last) * sign, inward(last, first) * sign); along += lengths[k]; } }