17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-13 08:20:59 +01:00

feat(editor): the drive of a switch stands by the tips of its blades, not at the joint before them, and goes with the switch deleted. A drive placed, changed or carried along with its switch is set where the blades begin, as they're shaped or where the diverging track is broken at the tip of a planed blade, along the main track there, its heading and its tilt taken there; the drives the earlier versions of the editor set at the joints are moved there once, as the scenery is opened and the templates are known, with no step of the undo history, to be saved with it. Deleting a switch, a double switch or a set of tracks takes the drives of the switches along in the same step, Ctrl+Z brings them back. The joint before the blades and the tips of the blades of the switch being placed or the one selected are marked across the track in the view, so the tips seen on the photo aren't taken for the start of the switch. Selftests bladetips, drivetips; on a copy of test42 the drive of z1 went from the joint to 1.248 m, deleted with z1, back with Ctrl+Z

This commit is contained in:
maj00r
2026-10-26 20:40:37 +01:00
parent 3b43c71dbb
commit ef592e6479
9 changed files with 174 additions and 4 deletions

View File

@@ -1346,6 +1346,21 @@ bool editor_mode::update()
{
Global.editor_tracks = (current_work_area() == work_area::tracks);
selftest_step();
// the drives of the switches go to the tips of their blades once, as the templates of the scenery are known
if (false == m_drives_checked)
{
EditorIncludes.start();
if (EditorIncludes.scanned())
{
m_drives_checked = true;
if (auto const moved{drives_to_blade_tips()}; moved > 0)
{
auto const text{format(STR_P("The drive of %zu switch is moved to the tips of its blades", "The drives of %zu switches are moved to the tips of their blades", static_cast<long long>(moved)), moved)};
ui()->set_status(text, false);
WriteLog("Editor: " + text, logtype::generic);
}
}
}
// after the tool has told its own status
if (false == m_orphan_note.empty())
{
@@ -1560,6 +1575,7 @@ bool editor_mode::update()
draw_track_set();
draw_track_spread();
draw_build_overlay();
draw_blade_tips();
draw_track_intent();
draw_tape();
m_speed.update();

View File

@@ -1714,6 +1714,8 @@ class editor_mode : public application_mode, private editor_track::observer, pri
bool extend_snap(editor_track::snap_target &Target) const;
void update_build_tools();
void draw_build_overlay() const;
// the joint before the blades of the switch being placed or the one selected and the tips of its blades
void draw_blade_tips() const;
// modes of the track editor, one at a time: path is the plain selection
using track_tab = editor_track_tab;
struct track_mode
@@ -2089,6 +2091,12 @@ class editor_mode : public application_mode, private editor_track::observer, pri
void apply_switch_drive_choice(TTrack &Switch);
void switch_drive_captured(TTrack const &Track);
void switch_drives_committed(std::vector<TTrack *> const &Tracks);
// the drives of the switches deleted go with them, in the step of the undo history the deletion made last
void drives_deleted(std::vector<TTrack *> const &Switches);
// the drives of all the switches of the scenery set at the tips of their blades, the ones the earlier versions of the editor set at the
// joint before them too: once, as the scenery is opened, with no step of the undo history; how many moved
std::size_t drives_to_blade_tips();
bool m_drives_checked{false};
scene::layer_handle output_layer(std::string const &Part) override;
// places them in the active layer or the given one, as one step of the undo. returns: how many were placed
std::size_t place_templates(std::vector<template_item> const &Items, std::string &Error, scene::layer_handle Layer = scene::layer_handle{}) override;

View File

@@ -50,6 +50,28 @@ http://mozilla.org/MPL/2.0/.
editor_mode::selftest_result editor_mode::selftest_switch(std::string const &command, std::istringstream &words)
{
// each switch: the tips of its blades and its drive from the joint before them; drivetips moves the drives there first
if (command == "bladetips" || command == "drivetips")
{
if (command == "drivetips")
WriteLog(format("SELFTEST drivetips: %zu moved", drives_to_blade_tips()));
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch || track->m_paths.size() < 2)
continue;
auto const start{track->m_paths[0].points[segment_data::point::start]};
std::string text{format("SELFTEST drive of %s: blades from %.3f m", track->name().c_str(), editor_track::blade_tip_of(*track))};
for (int path = 0; path < 2; ++path)
if (auto const &shape{track->blade(path)}; shape.valid)
text += format(", blade %d %.3f - %.3f m", path, shape.tip, shape.heel);
if (auto const found{find_switch_drive(*track)})
text += format(", drive %.3f m from the joint", geometry::plan_distance(found->standing.location, start));
else
text += ", no drive";
WriteLog(text);
}
return selftest_result::handled;
}
if (command == "curvedswitch")
{
std::string name;

View File

@@ -292,6 +292,41 @@ bool editor_mode::replace_switch_drive(TTrack &Switch, int const Family, bool co
return true;
}
void editor_mode::drives_deleted(std::vector<TTrack *> const &Switches)
{
if (m_history.empty())
return;
for (auto const *track : Switches)
{
if (track == nullptr || track->eType != tt_Switch)
continue;
auto const found{find_switch_drive(*track)};
if (false == found.has_value() || false == scene::Layers.removable(found->standing.instance) || scene::Layers.instance(found->standing.instance).removed)
continue;
scene::Layers.removed(found->standing.instance, true);
m_history.back().instances.push_back(found->standing.instance);
}
}
std::size_t editor_mode::drives_to_blade_tips()
{
// the drives the earlier versions of the editor set at the joint before the blades among them
std::vector<TTrack *> switches;
std::vector<std::pair<scene::instance_handle, std::string>> before;
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch)
continue;
if (auto const found{find_switch_drive(*track)}; found.has_value() && scene::Layers.removable(found->standing.instance))
{
switches.push_back(track);
before.emplace_back(found->standing.instance, scene::Layers.directive(found->standing.instance));
}
}
switch_drives_committed(switches);
return static_cast<std::size_t>(std::count_if(before.begin(), before.end(), [](auto const &Before) { return scene::Layers.directive(Before.first) != Before.second; }));
}
bool editor_mode::render_switch_drive_choice()
{
auto &tool{m_switch};

View File

@@ -1641,6 +1641,7 @@ void editor_mode::delete_selected_track()
if (auto const note{designs_trimmed({set->first, set->link, set->second})}; false == note.empty())
ui()->set_status(note, false);
push_track_snapshot({}, created, {set->first, set->link, set->second});
drives_deleted({set->first, set->second});
if (std::any_of(m_route.chain.tracks.begin(), m_route.chain.tracks.end(), [&](TTrack const *Each) { return Each == set->first || Each == set->link || Each == set->second; }))
m_route = {};
m_node = nullptr;
@@ -1666,6 +1667,7 @@ void editor_mode::delete_selected_track()
if (auto const note{designs_trimmed({track})}; false == note.empty())
ui()->set_status(note, false);
push_track_snapshot({}, created, {track});
drives_deleted({track});
// the model and the events of a turntable can't stay without its track
if (track->eType == tt_Table)
for (auto const instance : turntable_includes(*track))
@@ -2264,6 +2266,53 @@ void editor_mode::draw_track_hints()
foreground->AddText(at, IM_COL32(255, 255, 255, 255), readout.c_str());
}
void editor_mode::draw_blade_tips() const
{
segment_data main;
double tip{0.0};
if (m_switch.placing && m_switch.connection < 0)
{
auto const preview{switch_preview()};
if (preview.size() < 2)
return;
main = preview[0];
tip = editor_track::blade_tip_of(preview[1]);
}
else if (auto const *selected{selected_track()}; selected != nullptr && selected->eType == tt_Switch && selected->m_paths.size() >= 2 && false == selected->m_editorremoved)
{
main = selected->m_paths[0];
tip = editor_track::blade_tip_of(*selected);
}
else
return;
// on the photo the tips of the blades are seen where the switch seems to begin, the joint before them isn't
screen_projection const projection;
auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
geometry::bezier const curve{main};
auto const mark = [&](double const Distance, ImU32 const Colour, float const Width, char const *Label, int const Side) {
auto const t{curve.parameter(Distance)};
auto const at{curve.point(t)};
auto direction{plan_of(curve.first(t))};
if (glm::length(direction) < 1e-9)
return;
direction = glm::normalize(direction);
glm::dvec3 const across{-direction.y * 1.6, 0.0, direction.x * 1.6};
projection.line(drawlist, at - across, at + across, IM_COL32(0, 0, 0, 170), Width + 3.0f);
projection.line(drawlist, at - across, at + across, Colour, Width);
ImVec2 screen;
if (projection.project(at + across * static_cast<double>(Side), screen))
{
auto const size{ImGui::CalcTextSize(Label)};
ImVec2 const corner{Side > 0 ? screen.x + 6.0f : screen.x - 6.0f - size.x, screen.y - 0.5f * size.y};
drawlist->AddRectFilled({corner.x - 3.0f, corner.y - 1.0f}, {corner.x + size.x + 3.0f, corner.y + size.y + 1.0f}, IM_COL32(0, 0, 0, 160), 3.0f);
drawlist->AddText(corner, Colour, Label);
}
};
mark(0.0, IM_COL32(220, 220, 220, 230), 2.0f, STR_C("joint before the blades"), -1);
if (tip > 0.05)
mark(tip, IM_COL32(255, 170, 40, 255), 3.0f, STR_C("tips of the blades"), 1);
}
void editor_mode::draw_build_overlay() const
{
screen_projection const projection;

View File

@@ -1831,6 +1831,7 @@ void editor_mode::track_set_delete()
}
auto const designs{designs_trimmed(removed)};
push_track_snapshot({}, {}, removed);
drives_deleted(removed);
m_track_set.clear();
m_node = nullptr;
ui()->set_node(nullptr);

View File

@@ -208,7 +208,12 @@ class editor_track
glm::dvec2 tilt{0.0};
bool rightswitch{false};
};
// the drive of the switch by the tips of its blades, where the rods throw them over
static std::optional<drive_spot> drive_spot_of(TTrack const &Switch);
// how far along the main track of the switch the tips of its blades lie, where the pre-blade stretch from the joint ends: as the
// blades are shaped, else where the diverging track is broken at the tip of a blade planed back from the start; 0 at the start
static double blade_tip_of(TTrack const &Switch);
static double blade_tip_of(segment_data const &Diverging);
struct straight_axis
{

View File

@@ -299,6 +299,26 @@ glm::dvec3 editor_track::control_vector(TTrack const &Track, point_ref const &Co
return point_position(Track, Control) - point_position(Track, end_of(Control));
}
double editor_track::blade_tip_of(segment_data const &Diverging)
{
// the tip of a planed blade lies some metres from the start at most, a longer first piece is a part of the curve
if (Diverging.pieces.size() < 2)
return 0.0;
auto const length{bezier{Diverging.pieces.front()}.plan_length()};
return length < 5.0 ? length : 0.0;
}
double editor_track::blade_tip_of(TTrack const &Switch)
{
if (Switch.eType != tt_Switch || Switch.m_paths.size() < 2)
return 0.0;
double tip{0.0};
for (int path = 0; path < 2; ++path)
if (auto const &shape{Switch.blade(path)}; shape.valid && shape.tip > 0.0)
tip = tip > 0.0 ? std::min(tip, shape.tip) : shape.tip;
return tip > 0.0 ? tip : blade_tip_of(Switch.m_paths[1]);
}
std::optional<editor_track::drive_spot> editor_track::drive_spot_of(TTrack const &Switch)
{
if (Switch.eType != tt_Switch || Switch.m_paths.size() < 2)
@@ -306,8 +326,10 @@ std::optional<editor_track::drive_spot> editor_track::drive_spot_of(TTrack const
auto const &main{Switch.m_paths[0]};
auto const &diverging{Switch.m_paths[1]};
auto const start{main.points[segment_data::point::start]};
auto const &control{main.points[segment_data::point::control1]};
auto const along{control != glm::dvec3{} ? control : main.points[segment_data::point::end] - start};
// by the tips of the blades, not at the joint before them
bezier const curve{main};
auto const at{curve.parameter(blade_tip_of(Switch))};
auto const along{curve.first(at)};
auto heading{plan_of(along)};
if (glm::length(heading) < 1e-9)
return std::nullopt;
@@ -315,11 +337,11 @@ std::optional<editor_track::drive_spot> editor_track::drive_spot_of(TTrack const
glm::dvec2 const right{-heading.y, heading.x};
drive_spot spot;
spot.rightswitch = glm::dot(plan_of(diverging.points[segment_data::point::end] - start), right) > 0.0;
spot.location = start;
spot.location = curve.point(at);
auto const yaw{glm::degrees(std::atan2(heading.x, heading.y))};
spot.yaw = yaw < 0.0 ? yaw + 360.0 : yaw;
auto const run{glm::length(plan_of(along))};
auto const roll{static_cast<double>(main.rolls[0])};
auto const roll{glm::mix(static_cast<double>(main.rolls[0]), static_cast<double>(main.rolls[1]), at)};
spot.tilt = {-glm::degrees(std::atan(along.y / run)), roll};
if (Global.bRollFix)
spot.location.y += 0.75 * std::abs(std::sin(glm::radians(roll)));

View File

@@ -12303,3 +12303,15 @@ msgid_plural "The designs kept for the lines are cut down to the tracks left alo
msgstr[0] "Projekt linii zapisany w scenerii przycięto do torów, które przy nim zostały"
msgstr[1] "Projekty linii zapisane w scenerii przycięto do torów, które przy nich zostały"
msgstr[2] "Projekty linii zapisane w scenerii przycięto do torów, które przy nich zostały"
msgid "joint before the blades"
msgstr "styk przediglicowy"
msgid "tips of the blades"
msgstr "ostrza iglic"
msgid "The drive of %zu switch is moved to the tips of its blades"
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"