From f8a12107a0a2489de8bf1ebab5701f369d284013 Mon Sep 17 00:00:00 2001 From: maj00r Date: Tue, 27 Oct 2026 19:33:23 +0100 Subject: [PATCH] feat(editor): a switch laid with no track there, and no switch laid by a click: a press on the ground with a switch armed lays the switch there, its start where it was pressed, the direction of its main track taken from the drag once it's 4 m long and held, the drag sideways then giving the side of the diverging track, back by the start the direction free again; pressed by a free end within 5 m it goes on from that end in its direction and grade, joined to it. Its ends are left free to lay on from, styled after the track it goes on from or as the lay tool lays its tracks (editor_track::create_switch with a path_style). A press let go within 10 pixels places no switch, a slip at a crossing included, which was laid at once on the press: it's laid once dragged, its straights shown meanwhile. The hints and the panel tell of both. Selftest switchfree: Rz 60E1-300-1:9 on the ground with its four ends free, from the free end of a straight joined and walked through; a click on a straight laid none, a drag laid z1 --- application/editormode.cpp | 16 ++- application/editormode.h | 19 ++- application/editormodelay.cpp | 12 ++ application/editormodeselftestswitch.cpp | 43 +++++++ application/editormodeswitchplace.cpp | 144 ++++++++++++++++++++++- application/editormodeswitchtool.cpp | 6 +- application/editormodetrack.cpp | 11 +- application/editormodetrackshared.h | 2 + application/editormodetrackux.cpp | 24 +++- editor/editorTrack.cpp | 27 +++++ editor/editorTrack.hpp | 2 + lang/editor_pl.po | 51 ++++++++ 12 files changed, 342 insertions(+), 15 deletions(-) diff --git a/application/editormode.cpp b/application/editormode.cpp index e18a2520..6832aa49 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -6865,7 +6865,21 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const if (m_extend.active) finish_extend(); if (m_switch.placing) - finish_switch_placement(); + { + // a press let go with no drag places no switch, a click on the ground with no track there selects nothing + auto const &io{ImGui::GetIO()}; + if (glm::length(glm::vec2{io.MousePos.x, io.MousePos.y} - m_switch.pressed_at) <= 10.0f) + { + m_switch.placing = false; + if (m_switch.free) + { + m_switch.free = false; + track_click_select(nullptr); + } + } + else + finish_switch_placement(); + } if (m_turntable.placing) turntable_finish_placement(); if (m_signal.placing || m_signal.moving) diff --git a/application/editormode.h b/application/editormode.h index b821f601..ba029e4c 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1104,6 +1104,8 @@ class editor_mode : public application_mode, private editor_track::observer, pri static std::vector double_paths(double_switch const &Set); // the switch of the template, or the two switches of a double one with the plain track between them, committed and recorded std::vector create_switch_set(editor_track::switch_template const &Shape, std::vector const &Paths, TTrack const &Style, std::string const &Name); + // with no Style, as the lay tool lays its tracks + std::vector create_switch_set(editor_track::switch_template const &Shape, std::vector const &Paths, TTrack const *Style, std::string const &Name); void record_slip(std::vector const &Parts, std::string const &Kind, std::string const &Type, double const Radius, double const Angle, std::string const &Base, double const Middle = 0.0); void render_switch_record(TTrack const &Track) const; void route_store(); @@ -1267,6 +1269,11 @@ class editor_mode : public application_mode, private editor_track::observer, pri { std::vector keep_names; // the names the switches laid next take, before the ones of the tool bool curved{false}; + // a switch placed where there's no track: started on the ground, or at the free end it goes on from in its direction; the direction + // of its main track taken from the drag once it's long enough, then the drag sideways gives the side of the diverging one + bool free{false}; + editor_track::snap_target free_end; + std::optional free_heading; std::vector frame; std::vector frame_tracks; std::vector templates; @@ -1274,6 +1281,8 @@ class editor_mode : public application_mode, private editor_track::observer, pri int armed{-1}; int last{0}; // template armed the last time bool placing{false}; + // where it was pressed on the screen: let go no farther from there, the press places nothing, a click doesn't lay a switch by chance + glm::vec2 pressed_at{0.f}; double along{0.0}; double pressed{0.0}; // along a curve: where the press was, the start of the switch drawn from it to the ends of the cant ramps glm::dvec3 point{0.0}; @@ -1565,6 +1574,14 @@ class editor_mode : public application_mode, private editor_track::observer, pri void render_rail_profile_hint(TTrack &Track); bool start_switch_placement(); void finish_switch_placement(); + // a switch with no track there: where a press on the ground starts it, at the free end of a track within reach or right there + editor_track::snap_target free_switch_end(glm::dvec3 const &Ground) const; + bool free_switch_offered() const; + void start_free_switch(glm::dvec3 const &Ground); + // the start of the switch, the way its main track goes and the side of its diverging one, as the drag gives them so far + void free_switch_pose(glm::dvec3 &Start, glm::dvec2 &Heading, int &Side, double &Grade) const; + std::vector free_switch_paths() const; + void insert_free_switch(); std::vector switch_preview() const; void insert_switch(editor_track::straight const &Line, double const Along, int const Direction, int const Side); void switch_placed(std::vector const &Created); @@ -1815,7 +1832,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri void draw_track_hover() const; struct track_intent { - enum class kind { none, gizmo, select, point, box, straight_set, straight_break, detour, straight_tool, straight_handle, route_vertex, route_grip, switch_at, lay, lay_end, sweep, turntable_place, turntable_exit, turntable_fit, spread, extend_end, signal_place, signal_move, switch_slide, pick_style, fouling, measure, route_trace, kilometrage, srk_boundary }; + enum class kind { none, gizmo, select, point, box, straight_set, straight_break, detour, straight_tool, straight_handle, route_vertex, route_grip, switch_at, switch_free, lay, lay_end, sweep, turntable_place, turntable_exit, turntable_fit, spread, extend_end, signal_place, signal_move, switch_slide, pick_style, fouling, measure, route_trace, kilometrage, srk_boundary }; kind what{kind::none}; TTrack *track{nullptr}; int path{0}; diff --git a/application/editormodelay.cpp b/application/editormodelay.cpp index ad7ad4f7..934bbce6 100644 --- a/application/editormodelay.cpp +++ b/application/editormodelay.cpp @@ -102,7 +102,19 @@ void editor_mode::update_build_tools() joining(m_extend.snap); } if (m_switch.placing) + { m_switch.mouse = cursor_level(m_switch.point.y); + // a switch with no track takes the direction of its main track once the drag is long enough, dragged sideways after that it + // takes the side; back by its start the direction is free again + if (auto &tool{m_switch}; tool.free && tool.free_end.track == nullptr) + { + auto const offset{plan_of(tool.mouse - tool.point)}; + if (glm::length(offset) < kFreeSwitchRelease) + tool.free_heading.reset(); + else if (false == tool.free_heading.has_value() && glm::length(offset) >= kFreeSwitchLock) + tool.free_heading = glm::normalize(offset); + } + } auto &lay{m_lay}; lay.preview.clear(); lay.preview_error.clear(); diff --git a/application/editormodeselftestswitch.cpp b/application/editormodeselftestswitch.cpp index 31f4ce77..ab881fbc 100644 --- a/application/editormodeselftestswitch.cpp +++ b/application/editormodeselftestswitch.cpp @@ -481,6 +481,49 @@ editor_mode::selftest_result editor_mode::selftest_switch(std::string const &com model->location().x, model->location().y, model->location().z, path.points[segment_data::point::start].y)); return selftest_result::handled; } + if (command == "switchfree") + { + // a switch with no track: pressed at the point, the direction of its main track toward the second one (none at a free end), let + // go at the third one, whose side it takes + std::string label; + glm::dvec3 press{0.0}; + glm::dvec3 heading{0.0}; + glm::dvec3 release{0.0}; + words >> label >> press.x >> press.z >> heading.x >> heading.z >> release.x >> release.z; + std::replace(label.begin(), label.end(), '_', ' '); + auto &tool{m_switch}; + show_track_tab(track_tab::turnout); + tool.templates = editor_track::standard_switch_templates(); + tool.collected = true; + tool.connection = -1; + tool.armed = -1; + for (int i = 0; i < static_cast(tool.templates.size()); ++i) + if (tool.templates[i].label.rfind(label, 0) == 0) + { + tool.armed = i; + break; + } + start_free_switch(press); + if (false == tool.placing) + { + WriteLog("SELFTEST switchfree: not started, no template " + label); + return selftest_result::handled; + } + if (tool.free_end.track == nullptr) + tool.free_heading = glm::normalize(geometry::plan_of(heading - tool.point)); + tool.mouse = glm::dvec3{release.x, tool.point.y, release.z}; + tool.placing = false; + finish_switch_placement(); + WriteLog("SELFTEST switchfree" + (tool.free_end.track != nullptr ? " from " + tool.free_end.track->name() : std::string{}) + ": " + tool.status); + if (auto const *track{selected_track()}; track != nullptr && track->eType == tt_Switch) + for (std::size_t i = 0; i < track->m_paths.size(); ++i) + { + auto const &a{track->m_paths[i].points[segment_data::point::start]}; + auto const &b{track->m_paths[i].points[segment_data::point::end]}; + WriteLog(format("SELFTEST switchfree path %s/%zu: %.3f %.3f %.3f -> %.3f %.3f %.3f", track->name().c_str(), i, a.x, a.y, a.z, b.x, b.y, b.z)); + } + return selftest_result::handled; + } if (command == "switchplace" || command == "switchcurve" || command == "slipplace") { std::string label; diff --git a/application/editormodeswitchplace.cpp b/application/editormodeswitchplace.cpp index 865ed645..6e662d71 100644 --- a/application/editormodeswitchplace.cpp +++ b/application/editormodeswitchplace.cpp @@ -49,6 +49,8 @@ std::vector editor_mode::switch_preview() const auto const &tool{m_switch}; if (false == tool.placing || tool.armed < 0 || tool.armed >= static_cast(tool.templates.size())) return {}; + if (tool.free) + return free_switch_paths(); if (tool.curved) { int forward; @@ -56,6 +58,20 @@ std::vector editor_mode::switch_preview() const curved_switch_pose(forward, side); return curved_switch_paths(tool.templates[tool.armed], tool.frame, curved_switch_start(forward), forward, side); } + if (tool.templates[tool.armed].double_slip) + { + // a slip at the crossing where it was pressed: its two straights, laid there once dragged + slip_plan plan; + if (false == plan_double_slip(tool.line, tool.point, tool.templates[tool.armed], plan) || plan.replaces != nullptr) + return {}; + auto const straight = [](glm::dvec3 const &From, glm::dvec3 const &To) { + segment_data path; + path.points[segment_data::point::start] = From; + path.points[segment_data::point::end] = To; + return path; + }; + return {straight(plan.points[0], plan.points[1]), straight(plan.points[2], plan.points[3])}; + } if (switch_link plan; plan_switch_link(plan)) { plan.paths.insert(plan.paths.end(), plan.link.pieces.begin(), plan.link.pieces.end()); @@ -80,6 +96,7 @@ bool editor_mode::start_switch_placement() return false; glm::dvec3 const ground{cursor_level(selected->m_paths.front().points[segment_data::point::start].y)}; tool.curved = false; + tool.free = false; tool.status.clear(); if (current_straight().tracks.empty()) { @@ -141,7 +158,12 @@ bool editor_mode::start_switch_placement() return false; if (tool.templates[tool.armed].double_slip) { - insert_double_slip(line, cursor, tool.templates[tool.armed]); + // at the crossing where it was pressed, laid only once dragged, as a switch is + tool.along = along; + tool.point = cursor; + tool.mouse = cursor; + tool.line = line; + tool.placing = true; return true; } tool.along = along; @@ -155,6 +177,12 @@ bool editor_mode::start_switch_placement() void editor_mode::finish_switch_placement() { auto &tool{m_switch}; + if (tool.free) + { + tool.placing = false; + insert_free_switch(); + return; + } if (tool.connection >= 0) { if (false == tool.curved) @@ -170,6 +198,11 @@ void editor_mode::finish_switch_placement() tool.placing = false; if (tool.armed < 0 || tool.armed >= static_cast(tool.templates.size())) return; + if (tool.templates[tool.armed].double_slip && false == tool.curved) + { + insert_double_slip(tool.line, tool.point, tool.templates[tool.armed]); + return; + } if (linked) { insert_linked_switch(plan); @@ -207,6 +240,98 @@ void editor_mode::insert_switch(editor_track::straight const &Line, double const } +bool editor_mode::free_switch_offered() const +{ + auto const &tool{m_switch}; + return m_track_tab == track_tab::turnout && tool.connection < 0 && tool.armed >= 0 && tool.armed < static_cast(tool.templates.size()) && false == tool.templates[tool.armed].double_slip; +} + +editor_track::snap_target editor_mode::free_switch_end(glm::dvec3 const &Ground) const +{ + return editor_track::find_free_end(nullptr, kRailCategory, Ground, kLaySnapRadius, {}); +} + +void editor_mode::start_free_switch(glm::dvec3 const &Ground) +{ + auto &tool{m_switch}; + if (false == free_switch_offered()) + return; + tool.status.clear(); + tool.curved = false; + tool.line = {}; + tool.free = true; + tool.free_end = free_switch_end(Ground); + tool.free_heading.reset(); + if (tool.free_end.track != nullptr) + { + // going on from the free end in its direction + auto const outward{plan_of(-tool.free_end.direction)}; + if (glm::length(outward) > 1e-9) + tool.free_heading = glm::normalize(outward); + tool.point = tool.free_end.position; + } + else + tool.point = lay_bed(Ground); + tool.mouse = tool.point; + tool.placing = true; +} + +void editor_mode::free_switch_pose(glm::dvec3 &Start, glm::dvec2 &Heading, int &Side, double &Grade) const +{ + auto const &tool{m_switch}; + Start = tool.point; + glm::dvec2 const offset{plan_of(tool.mouse - tool.point)}; + // till the drag is long enough to take the direction from, it follows the cursor, the diverging track to the left + Heading = tool.free_heading.value_or(glm::length(offset) > 1e-6 ? glm::normalize(offset) : glm::dvec2{0.0, 1.0}); + Side = cross(Heading, offset) >= 0.0 ? 1 : -1; + Grade = 0.0; + if (tool.free_end.track != nullptr) + { + // the grade of the track it goes on from + auto const &into{tool.free_end.direction}; + auto const run{glm::length(plan_of(into))}; + Grade = run > 1e-9 ? -into.y / run : 0.0; + } +} + +std::vector editor_mode::free_switch_paths() const +{ + auto const &tool{m_switch}; + if (tool.armed < 0 || tool.armed >= static_cast(tool.templates.size())) + return {}; + glm::dvec3 start; + glm::dvec2 heading; + int side; + double grade; + free_switch_pose(start, heading, side, grade); + return editor_track::place_switch(tool.templates[tool.armed], start, heading, side, grade); +} + +void editor_mode::insert_free_switch() +{ + auto &tool{m_switch}; + tool.free = false; + // a click with no drag on the ground gives no direction; from a free end it's the end's + if (tool.free_end.track == nullptr && false == tool.free_heading.has_value()) + { + tool.status = refused(STR_C("Drag along the main track of the switch, then sideways to the side of the diverging one")); + return; + } + auto const paths{free_switch_paths()}; + if (paths.size() < 2) + return; + auto const made{create_switch_set(tool.templates[tool.armed], paths, tool.free_end.track, switch_name_for_new())}; + if (made.empty()) + return; + push_track_snapshot({}, made, {}); + straight_refresh(); + switch_placed(made); + auto const text{tool.free_end.track != nullptr ? format(STR_C("Switch %s laid on from %s, its other ends free: lay on from them"), made.front()->name().c_str(), tool.free_end.track->name().c_str()) + : format(STR_C("Switch %s laid with no track, its ends free: lay on from them"), made.front()->name().c_str())}; + tool.status = text + (tool.status.empty() ? std::string{} : "\n" + tool.status); + ui()->set_status(tool.status, false); +} + void editor_mode::switch_placed(std::vector const &Created) { auto const found{std::find_if(Created.begin(), Created.end(), [](TTrack const *Track) { return Track != nullptr && Track->eType == tt_Switch; })}; @@ -424,15 +549,24 @@ bool editor_mode::place_switch_on_straight(editor_track::straight const &Line, e std::vector editor_mode::create_switch_set(editor_track::switch_template const &Shape, std::vector const &Paths, TTrack const &Style, std::string const &Name) { + return create_switch_set(Shape, Paths, &Style, Name); +} + +std::vector editor_mode::create_switch_set(editor_track::switch_template const &Shape, std::vector const &Paths, TTrack const *Style, std::string const &Name) +{ + // with no track to copy, styled as the lay tool lays its tracks + auto const switch_of = [&](editor_track::switch_template const &Template, std::vector const &Each, std::string const &Named) { + return Style != nullptr ? editor_track::create_switch(Template, Each, *Style, Named) : editor_track::create_switch(Template, Each, m_lay.style, Named); + }; if (Shape.twofold() && Paths.size() >= 5) { auto firstshape{Shape}; firstshape.length = glm::distance(Paths[0].points[segment_data::point::start], Paths[0].points[segment_data::point::end]); auto secondshape{Shape}; secondshape.length = Shape.length - Shape.stagger; - auto *one{editor_track::create_switch(firstshape, Paths, Style, Name)}; - auto *two{one != nullptr ? editor_track::create_switch(secondshape, {Paths[2], Paths[3]}, Style, Name + "b") : nullptr}; - auto *link{two != nullptr ? editor_track::create_path(Style, Paths[4]) : nullptr}; + auto *one{switch_of(firstshape, Paths, Name)}; + auto *two{one != nullptr ? switch_of(secondshape, {Paths[2], Paths[3]}, Name + "b") : nullptr}; + auto *link{two == nullptr ? nullptr : Style != nullptr ? editor_track::create_path(*Style, Paths[4]) : editor_track::create_path(m_lay.style, Paths[4])}; if (link == nullptr) { m_switch.status = refused(STR_C("The switch couldn't be created from this template")); @@ -443,7 +577,7 @@ std::vector editor_mode::create_switch_set(editor_track::switch_templa m_switch.status = format(STR_C("Double switch %s with its second switch %s inserted"), one->name().c_str(), two->name().c_str()); return {one, two, link}; } - auto *track{editor_track::create_switch(Shape, Paths, Style, Name)}; + auto *track{switch_of(Shape, Paths, Name)}; if (track == nullptr) { m_switch.status = refused(STR_C("The switch couldn't be created from this template")); diff --git a/application/editormodeswitchtool.cpp b/application/editormodeswitchtool.cpp index 41f163a5..014e56a4 100644 --- a/application/editormodeswitchtool.cpp +++ b/application/editormodeswitchtool.cpp @@ -139,7 +139,7 @@ std::string template_tooltip(editor_track::switch_template const &Template) text += "\n"; text += Template.type == "Rkp" ? STR_C("Single slip (Rkp)") : Template.type == "Rkpd" ? STR_C("Double slip (Rkpd)") : STR_C("Crossing (Skrz)"); text += "\n" + format(STR_C("a %.3f m from the middle of the crossing to the joint"), Template.a) + "\n"; - text += Template.type == "Rkp" ? STR_C("Click at a crossing of two straights, on the side of the tracks to join") : STR_C("Click at a crossing of two straights"); + text += Template.type == "Rkp" ? STR_C("Drag at a crossing of two straights, on the side of the tracks to join") : STR_C("Drag at a crossing of two straights"); return text; } text += "\n" + format(STR_C("a %.3f m to the centre of the switch, b %.3f m on to its end"), Template.a, Template.b); @@ -275,13 +275,13 @@ void editor_mode::render_switch_ui() switch (kind) { case switch_kind::slip: - case switch_kind::crossing: how = {{"LMB on a crossing", "of two straights: it goes there"}}; break; + case switch_kind::crossing: how = {{"Drag at a crossing", "of two straights: it goes there"}}; break; case switch_kind::connection: how = {{"Drag", "from a straight onto the other track"}, {"Shift+wheel", "the kind"}, {"Ctrl+wheel", "R of the aligning curve"}}; if (tool.connection == static_cast(connection::kind::double_crossover)) how.push_back({"Alt+wheel", "where they meet"}); break; - default: how = {{"Drag on a track", "along: the direction, sideways: the side"}}; break; + default: how = {{"Drag on a track", "along: the direction, sideways: the side"}, {"Drag on the ground", "a switch with no track, on from a free end near it"}}; break; } gesture_rows(how); diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 3f02fe31..a674a49f 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1727,6 +1727,9 @@ std::vector editor_mode::track_key_hints(bool const All) hints.push_back({"Ctrl+wheel", "radius"}); hints.insert(hints.end(), {{"Release", "build"}, {"Esc", "cancel"}}); } + else if (m_switch.placing && m_switch.free) + hints = m_switch.free_end.track != nullptr ? std::vector{{"Drag sideways", "side of the diverging track"}, {"Esc", "cancel"}} + : std::vector{{"Drag along", "the direction of the main track, held from 4 m on"}, {"Drag sideways", "side of the diverging track"}, {"Back to the start", "the direction free again"}, {"Esc", "cancel"}}; else if (m_switch.placing) hints = m_switch.connection >= 0 && false == m_switch.curved ? std::vector{{"Drag onto the other track", "the connection there"}, {"Shift+wheel", "another kind"}, {"Ctrl+wheel", "R of the aligning curve"}, {"Alt+wheel", "where a double crossover meets"}, {"Esc", "cancel"}} : @@ -1739,8 +1742,8 @@ std::vector editor_mode::track_key_hints(bool const All) { case switch_kind::connection: hints = {{"Press on a straight", "and drag onto the other track"}, {"Shift+wheel", "another kind"}, {"Ctrl+wheel", "R of the aligning curve"}}; break; case switch_kind::slip: - case switch_kind::crossing: hints = {{"LMB", "at a crossing of two straights"}}; break; - default: hints = {{"LMB on a track", "the straight or the curve for the switch"}}; break; + case switch_kind::crossing: hints = {{"Press+drag at a crossing", "of two straights"}}; break; + default: hints = {{"LMB on a track", "the straight or the curve for the switch"}, {"Press+drag on the ground", "a switch with no track, on from a free end near it"}}; break; } if (selected != nullptr) { @@ -2472,7 +2475,7 @@ void editor_mode::draw_build_overlay() const { marker = plan.replaces->location(); colour = overlay_color::highlight; - text = STR_C("Click: this double slip is replaced by the one chosen"); + text = STR_C("Press and drag: this double slip is replaced by the one chosen"); } else if (valid) { @@ -2485,7 +2488,7 @@ void editor_mode::draw_build_overlay() const } marker = {plan.crossing.x, line.start.y + line.grade * plan.alongfirst, plan.crossing.y}; colour = overlay_color::highlight; - text = plan.note.empty() ? std::string{STR_C("Click: it goes here")} : plan.note; + text = plan.note.empty() ? std::string{STR_C("Press and drag: it goes here")} : plan.note; } ImVec2 screen; if (projection.project(marker, screen)) diff --git a/application/editormodetrackshared.h b/application/editormodetrackshared.h index 00e5399b..9a239391 100644 --- a/application/editormodetrackshared.h +++ b/application/editormodetrackshared.h @@ -207,6 +207,8 @@ float const kHandleRadius{10.0f}; double const kLaySnapRadius{5.0}; float const kSnapPixels{16.0f}; double const kSwitchReach{6.0}; // a press farther from the selected track selects another one instead of placing the switch +double const kFreeSwitchLock{4.0}; // m of the drag a switch with no track takes the direction of its main track from +double const kFreeSwitchRelease{2.0}; // m from its start the drag of a switch with no track lets the direction go again double const kSlipReach{12.0}; // m; a slip goes to the crossing nearest the cursor, if it's this close // m, of the straights past the ends of a curve a crossover laid along it reaches onto: from a transition it goes on onto the // straight after it diff --git a/application/editormodetrackux.cpp b/application/editormodetrackux.cpp index 737d86e3..1beebf86 100644 --- a/application/editormodetrackux.cpp +++ b/application/editormodetrackux.cpp @@ -829,6 +829,14 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods) return make(kind::switch_at, "Press and drag: a switch here, along sets its direction, sideways its side"); } } + // a press on the ground lays a switch with no track there, going on from a free end within reach + if (m_hover.track == nullptr && free_switch_offered()) + { + auto const end{free_switch_end(ground)}; + intent.position = end.track != nullptr ? end.position : ground; + return make(kind::switch_free, end.track != nullptr ? "Press and drag: a switch going on from this free end, sideways its side" + : "Press and drag: a switch with no track, along the direction of its main track, then sideways its side"); + } if (m_track_tab == track_tab::lineside && m_fouling.tab().open) if (auto const hit{m_fouling.hit()}; hit.has_value()) { @@ -1022,7 +1030,20 @@ void editor_mode::track_press(track_intent const &Intent) m_route.grip = Intent.index; handle_drag_start(Intent.position); return; - case kind::switch_at: start_switch_placement(); return; + case kind::switch_at: + { + auto const &io{ImGui::GetIO()}; + m_switch.pressed_at = {io.MousePos.x, io.MousePos.y}; + start_switch_placement(); + return; + } + case kind::switch_free: + { + auto const &io{ImGui::GetIO()}; + m_switch.pressed_at = {io.MousePos.x, io.MousePos.y}; + start_free_switch(Intent.position); + return; + } case kind::kilometrage: if (Intent.track != nullptr) m_km.pick(*Intent.track, Intent.position); @@ -1099,6 +1120,7 @@ void editor_mode::draw_track_intent() const case kind::route_vertex: case kind::route_grip: drawlist->AddCircle(at, 13.0f, overlay_color::marked, 24, 2.5f); break; case kind::switch_at: + case kind::switch_free: case kind::straight_tool: drawlist->AddCircleFilled(at, 4.0f, overlay_color::grip); drawlist->AddCircle(at, 11.0f, overlay_color::grip, 20, 2.0f); diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index 11150cb6..5f4ed495 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -3159,6 +3159,33 @@ TTrack *editor_track::create_switch(switch_template const &Template, std::vector return track; } +TTrack *editor_track::create_switch(switch_template const &Template, std::vector const &Paths, path_style const &Style, std::string const &Name) +{ + if (Paths.size() < 2) + return nullptr; + if (Template.source != nullptr) + return create_switch(Template, Paths, *Template.source, Name); + std::ostringstream text; + text.precision(std::numeric_limits::digits10); + // the rails of the style on both paths, its ballast under the switch + text << "switch " << Template.length << " 1.435 0.15 10 20 0 flat vis " << Style.rail << " 6 " << Style.rail << " 0.2 0.5 1.1 "; + write_points(text, Paths[0]); + write_points(text, Paths[1]); + text << "trackbed " << Style.ballast << ' '; + if (Style.velocity > 0.0) + text << "velocity " << Style.velocity << ' '; + if (false == Style.rail_profile.empty()) + text << "railprofile " << Style.rail_profile << ' '; + text << "endtrack\n"; + auto *track{load_text(text.str(), nullptr, Name)}; + track->SwitchExtension->m_frogs = true; + track->SwitchExtension->m_movablenose = Template.movable; + track->SwitchExtension->m_bladeheel = Template.heel; + track->SwitchExtension->m_bladethrow = Template.bladethrow; + track->m_paths.assign(Paths.begin(), Paths.begin() + 2); + return track; +} + bool editor_track::touches(TTrack const &Track, glm::dvec3 const &Point) { for (auto const &path : Track.m_paths) diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index c2e6e530..6799d35b 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -512,6 +512,8 @@ class editor_track static glm::dvec3 point_at(TTrack const &Track, double const T); static std::vector place_switch(switch_template const &Template, glm::dvec3 const &Origin, glm::dvec2 const &Direction, int const Side, double const Grade); static TTrack *create_switch(switch_template const &Template, std::vector const &Paths, TTrack const &Style, std::string const &Name = {}); + // one laid where there's no track to copy, styled as the lay tool lays its tracks + static TTrack *create_switch(switch_template const &Template, std::vector const &Paths, path_style const &Style, std::string const &Name = {}); static TTrack *create_turntable(segment_data const &Path, TTrack const *Style, std::string const &Name); static void move_straights(std::vector const &Lines, std::vector> const &Ends); // Intervals: the path of the chain each piece takes the place of, else they're handed out in turn diff --git a/lang/editor_pl.po b/lang/editor_pl.po index bab22d1b..3f30b0b3 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -12315,3 +12315,54 @@ msgid_plural "The drives of %zu switches are moved to the tips of their blades" msgstr[0] "Napęd %zu rozjazdu przestawiony na ostrza iglic" msgstr[1] "Napędy %zu rozjazdów przestawione na ostrza iglic" msgstr[2] "Napędy %zu rozjazdów przestawione na ostrza iglic" + +msgid "Press and drag: a switch going on from this free end, sideways its side" +msgstr "Wciśnij i przeciągnij: rozjazd od tego wolnego końca, w bok jego strona" + +msgid "Press and drag: a switch with no track, along the direction of its main track, then sideways its side" +msgstr "Wciśnij i przeciągnij: rozjazd bez toru, wzdłuż kierunek toru zasadniczego, potem w bok jego strona" + +msgid "Drag along the main track of the switch, then sideways to the side of the diverging one" +msgstr "Przeciągnij wzdłuż toru zasadniczego rozjazdu, potem w bok na stronę toru zwrotnego" + +msgid "Switch %s laid on from %s, its other ends free: lay on from them" +msgstr "Rozjazd %s ułożony od %s, pozostałe końce wolne: kładź od nich dalej" + +msgid "Switch %s laid with no track, its ends free: lay on from them" +msgstr "Rozjazd %s ułożony bez toru, końce wolne: kładź od nich dalej" + +msgid "Press and drag: this double slip is replaced by the one chosen" +msgstr "Wciśnij i przeciągnij: ten rozjazd krzyżowy zostanie zastąpiony wybranym" + +msgid "Press and drag: it goes here" +msgstr "Wciśnij i przeciągnij: stanie tutaj" + +msgid "Press+drag at a crossing" +msgstr "Wciśnij i przeciągnij na skrzyżowaniu" + +msgid "of two straights" +msgstr "dwóch prostych" + +msgid "Press+drag on the ground" +msgstr "Wciśnij i przeciągnij na terenie" + +msgid "a switch with no track, on from a free end near it" +msgstr "rozjazd bez toru, od pobliskiego wolnego końca" + +msgid "the direction of the main track, held from 4 m on" +msgstr "kierunek toru zasadniczego, od 4 m zatrzaśnięty" + +msgid "Back to the start" +msgstr "Z powrotem do początku" + +msgid "the direction free again" +msgstr "kierunek znów wolny" + +msgid "Drag at a crossing" +msgstr "Przeciągnij na skrzyżowaniu" + +msgid "Drag at a crossing of two straights" +msgstr "Przeciągnij na skrzyżowaniu dwóch prostych" + +msgid "Drag at a crossing of two straights, on the side of the tracks to join" +msgstr "Przeciągnij na skrzyżowaniu dwóch prostych, po stronie torów do połączenia"