mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-07 18:04:47 +01:00
feat(editor): Signals mode of the Tracks window (H): semaphores, block, warning, shunting, stop, substitute signals and repeaters placed by the track from the templates of the scenery, described by //$e signal: (kind, mount, lamps, lean, the event the train reads them by) which tools/editor/signal_headers.py writes into them; a click by a track puts the signal on that side 2.25 m from the axis at the rail heads, facing the trains which have it on their right, a drag along turns it to the trains going the way of the drag; its (p1)_sem_info goes to event1 or event2 of the path, warning signals and repeaters, which the train doesn't read, get none; a head leaning away from the track swapped for its counterpart; the plate named like the signal; signals dragged along the track with their event, removed with it, renamed in the node properties with it, each a single step of the undo history; Polish translation
This commit is contained in:
@@ -195,6 +195,7 @@ set(SOURCES
|
||||
"application/editormodeparallel.cpp"
|
||||
"application/editormodelineside.cpp"
|
||||
"application/editormodevehicle.cpp"
|
||||
"application/editormodesignal.cpp"
|
||||
"application/editormodesweep.cpp"
|
||||
"application/editormodeturntable.cpp"
|
||||
"application/editormodeselftest.cpp"
|
||||
|
||||
@@ -1355,6 +1355,12 @@ bool editor_mode::update()
|
||||
point_drag_update();
|
||||
if (m_track_tab == track_tab::turntable)
|
||||
draw_turntable_overlay();
|
||||
if (m_track_tab == track_tab::signals)
|
||||
{
|
||||
signal_refresh();
|
||||
signal_move_update();
|
||||
draw_signal_overlay();
|
||||
}
|
||||
if (m_track_tab == track_tab::lineside)
|
||||
{
|
||||
draw_parallel_preview();
|
||||
@@ -2423,6 +2429,17 @@ void editor_mode::apply_include()
|
||||
auto const directive = editor_includes::compose_directive(m_include.target, m_include.values);
|
||||
if (directive == before)
|
||||
return;
|
||||
std::string read_before, read_after;
|
||||
if (false == m_include.info.signal_read.empty())
|
||||
{
|
||||
std::string file;
|
||||
std::vector<std::string> previous;
|
||||
if (editor_includes::parse_directive(before, file, previous))
|
||||
{
|
||||
read_before = editor_includes::substitute(m_include.info.signal_read, previous);
|
||||
read_after = editor_includes::substitute(m_include.info.signal_read, m_include.values);
|
||||
}
|
||||
}
|
||||
if (false == m_include_gesture)
|
||||
{
|
||||
// a single undo step for the whole drag
|
||||
@@ -2430,10 +2447,14 @@ void editor_mode::apply_include()
|
||||
snap.instance = m_instance;
|
||||
snap.node_name = *scene::Layers.instance(m_instance).file;
|
||||
snap.serialized = std::move(before);
|
||||
if (read_before != read_after)
|
||||
signal_renamed(read_before, read_after, snap);
|
||||
m_history.push_back(std::move(snap));
|
||||
g_redo.clear();
|
||||
m_include_gesture = true;
|
||||
}
|
||||
else if (read_before != read_after && false == m_history.empty())
|
||||
signal_renamed(read_before, read_after, m_history.back());
|
||||
set_include_directive(m_instance, directive);
|
||||
}
|
||||
|
||||
@@ -4209,6 +4230,10 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (is_press(Action) && m_instance != 0 && signal_delete(m_instance))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (is_press(Action) && m_instance != 0)
|
||||
{
|
||||
// include of a scenery template. what it shows goes out of sight, the directive is erased on save
|
||||
@@ -4534,6 +4559,8 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const
|
||||
finish_switch_placement();
|
||||
if (m_turntable.placing)
|
||||
turntable_finish_placement();
|
||||
if (m_signal.placing || m_signal.moving)
|
||||
signal_release();
|
||||
}
|
||||
|
||||
m_dragging = false;
|
||||
|
||||
@@ -29,6 +29,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "world/Sweep.h"
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
@@ -814,7 +815,7 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
void update_build_tools();
|
||||
void draw_build_overlay() const;
|
||||
// modes of the track editor, one at a time: path is the plain selection
|
||||
enum class track_tab { straights, route, path, turnout, profile, speed, joints, infra, lay, lineside, turntable };
|
||||
enum class track_tab { straights, route, path, turnout, profile, speed, joints, infra, lay, lineside, turntable, signals };
|
||||
struct track_mode
|
||||
{
|
||||
char const *label;
|
||||
@@ -822,7 +823,7 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
track_tab tab;
|
||||
char const *tooltip;
|
||||
};
|
||||
static std::array<track_mode, 11> const &track_modes();
|
||||
static std::array<track_mode, 12> const &track_modes();
|
||||
// the objects along the track: a tab for each tool
|
||||
int m_lineside_tab{0};
|
||||
void render_lineside_ui();
|
||||
@@ -878,7 +879,7 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
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 };
|
||||
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 };
|
||||
kind what{kind::none};
|
||||
TTrack *track{nullptr};
|
||||
int path{0};
|
||||
@@ -1043,6 +1044,7 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
bool described{false};
|
||||
std::string track;
|
||||
glm::dvec2 tilt{0.0}; // rot.x and rot.z of a described template, degrees
|
||||
std::map<std::string, std::string> values;
|
||||
};
|
||||
struct standing_template
|
||||
{
|
||||
@@ -1192,6 +1194,93 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
void render_vehicle_ui();
|
||||
void draw_vehicle_marker() const;
|
||||
|
||||
struct signal_template
|
||||
{
|
||||
std::string file;
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string kind;
|
||||
std::string mount;
|
||||
std::string lamps;
|
||||
std::string lean;
|
||||
std::string read;
|
||||
int parameters{0};
|
||||
bool plate{false};
|
||||
bool linked{false};
|
||||
};
|
||||
struct signal_spot
|
||||
{
|
||||
TTrack *track{nullptr};
|
||||
int path{0};
|
||||
glm::dvec3 axis{0.0};
|
||||
glm::dvec2 travel{0.0, 1.0};
|
||||
int side{1};
|
||||
int list{2};
|
||||
double offset{0.0};
|
||||
glm::dvec3 position{0.0};
|
||||
double yaw{0.0};
|
||||
};
|
||||
struct signal_standing
|
||||
{
|
||||
standing_template include;
|
||||
int kind{-1};
|
||||
std::string name;
|
||||
std::string read;
|
||||
TTrack *track{nullptr};
|
||||
int list{0};
|
||||
signal_spot spot;
|
||||
bool on_track{false};
|
||||
};
|
||||
struct signal_tool
|
||||
{
|
||||
std::vector<signal_template> templates;
|
||||
int revision{-1};
|
||||
int kind{0};
|
||||
std::array<int, 7> chosen{-1, -1, -1, -1, -1, -1, -1};
|
||||
char filter[64]{};
|
||||
char name[64]{"A"};
|
||||
char plate[64]{};
|
||||
char linked[64]{"none"};
|
||||
float height{0.0f};
|
||||
bool placing{false};
|
||||
signal_spot press;
|
||||
glm::vec2 pressed{0.f};
|
||||
int facing{0};
|
||||
std::vector<signal_standing> standing;
|
||||
std::size_t standing_history{static_cast<std::size_t>(-1)};
|
||||
std::size_t standing_redo{static_cast<std::size_t>(-1)};
|
||||
double standing_time{-1.0};
|
||||
int hover{-1};
|
||||
scene::instance_handle selected{0};
|
||||
bool moving{false};
|
||||
signal_spot moved;
|
||||
bool moved_valid{false};
|
||||
std::string status;
|
||||
};
|
||||
signal_tool m_signal;
|
||||
void signal_scan();
|
||||
signal_template const *signal_armed() const;
|
||||
signal_template const *signal_template_of(std::string const &File) const;
|
||||
bool signal_spot_at(TTrack &Track, int const Path, glm::dvec3 const &Point, glm::dvec3 const &Cursor, int const Facing, int const Side, signal_template const &Template, signal_spot &Spot) const;
|
||||
void signal_refresh(bool const Force = false);
|
||||
int signal_hit() const;
|
||||
void signal_press(track_intent const &Intent);
|
||||
void signal_release();
|
||||
bool signal_place(signal_spot const &Spot, signal_template const &Template);
|
||||
void signal_move_update();
|
||||
void signal_move_finish();
|
||||
bool signal_delete(scene::instance_handle const Instance);
|
||||
void signal_detach_events(std::string const &Read, std::vector<std::pair<TTrack *, editor_track::state>> &States);
|
||||
void signal_renamed(std::string const &Before, std::string const &After, EditorSnapshot &Snapshot);
|
||||
void signal_select(scene::instance_handle const Instance);
|
||||
std::string signal_next_name(std::string const &Name) const;
|
||||
void render_signal_ui();
|
||||
void render_signal_palette();
|
||||
void render_signal_selected();
|
||||
void draw_signal_overlay() const;
|
||||
std::string signal_intent_label(signal_template const &Template, signal_spot const &Spot) const;
|
||||
std::string signal_variant(signal_template const &Template, signal_spot const &Spot) const;
|
||||
|
||||
// a model laid along the track: bent to follow it, or repeated along it
|
||||
struct sweep_tool
|
||||
{
|
||||
|
||||
@@ -288,7 +288,7 @@ std::size_t editor_mode::place_templates(std::vector<template_item> const &Items
|
||||
include_info info;
|
||||
if (false == editor_includes::load(item.file, info, Error))
|
||||
continue;
|
||||
directive = editor_includes::directive(item.file, info, editor_includes::parameter_count(item.file), local, static_cast<float>(item.yaw), item.track, item.tilt);
|
||||
directive = editor_includes::directive(item.file, info, editor_includes::parameter_count(item.file), local, static_cast<float>(item.yaw), item.track, item.tilt, item.values);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -918,6 +918,152 @@ void editor_mode::selftest_step()
|
||||
std::getline(words, text);
|
||||
WriteLog("SELFTEST" + text);
|
||||
}
|
||||
else if (command == "signal" || command == "signalmove" || command == "signaldelete" || command == "signalrename" || command == "signalprobe")
|
||||
{
|
||||
std::map<std::string, std::string> values;
|
||||
std::string pair;
|
||||
while (words >> pair)
|
||||
{
|
||||
auto const equals{pair.find('=')};
|
||||
if (equals != std::string::npos)
|
||||
values[pair.substr(0, equals)] = pair.substr(equals + 1);
|
||||
}
|
||||
show_track_tab(track_tab::signals);
|
||||
signal_refresh(true);
|
||||
auto &tool{m_signal};
|
||||
auto const standing_named = [&](std::string const &Name) -> signal_standing const * {
|
||||
auto const found{std::find_if(tool.standing.begin(), tool.standing.end(), [&](signal_standing const &Standing) { return Standing.name == Name; })};
|
||||
return found != tool.standing.end() ? &*found : nullptr;
|
||||
};
|
||||
auto const spot_on = [&](signal_template const &Template, int const Facing, int const Side, signal_spot &Spot) {
|
||||
auto *track{simulation::Paths.find(values["track"])};
|
||||
if (track == nullptr)
|
||||
return false;
|
||||
auto const path{values.count("path") ? std::stoi(values["path"]) : 0};
|
||||
auto const at{values.count("at") ? std::stod(values["at"]) : 0.5};
|
||||
geometry::bezier const curve{track->m_paths[std::clamp(path, 0, static_cast<int>(track->m_paths.size()) - 1)]};
|
||||
return signal_spot_at(*track, path, curve.point(at), curve.point(at), Facing, Side, Template, Spot);
|
||||
};
|
||||
if (command == "signal")
|
||||
{
|
||||
auto const *item{signal_template_of(values["file"])};
|
||||
if (item == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST signal: no signal template " + values["file"]);
|
||||
continue;
|
||||
}
|
||||
for (int k = 0; k < static_cast<int>(tool.templates.size()); ++k)
|
||||
if (&tool.templates[k] == item)
|
||||
{
|
||||
static char const *const kinds[] = {"main", "block", "warning", "shunt", "stop", "substitute", "repeater"};
|
||||
for (int i = 0; i < 7; ++i)
|
||||
if (item->kind == kinds[i])
|
||||
{
|
||||
tool.kind = i;
|
||||
tool.chosen[i] = k;
|
||||
}
|
||||
}
|
||||
if (values.count("name"))
|
||||
std::snprintf(tool.name, sizeof(tool.name), "%s", values["name"].c_str());
|
||||
if (values.count("plate"))
|
||||
std::snprintf(tool.plate, sizeof(tool.plate), "%s", values["plate"].c_str());
|
||||
auto const side{values["side"] == "left" ? -1 : 1};
|
||||
auto const facing{values["facing"] == "along" ? 1 : values["facing"] == "against" ? -1 : 0};
|
||||
signal_spot spot;
|
||||
if (false == spot_on(*signal_armed(), facing, side, spot))
|
||||
{
|
||||
WriteLog("SELFTEST signal: no such track " + values["track"]);
|
||||
continue;
|
||||
}
|
||||
auto const placed{signal_place(spot, *signal_armed())};
|
||||
WriteLog(format("SELFTEST signal %s: %s", placed ? "placed" : "failed", tool.status.c_str()));
|
||||
}
|
||||
else if (command == "signalmove")
|
||||
{
|
||||
auto const *standing{standing_named(values["name"])};
|
||||
auto const *item{standing != nullptr ? signal_template_of(standing->include.file) : nullptr};
|
||||
if (item == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST signalmove: no signal " + values["name"]);
|
||||
continue;
|
||||
}
|
||||
auto const yaw{glm::radians(standing->include.yaw + 180.0)};
|
||||
glm::dvec2 const travel{std::sin(yaw), std::cos(yaw)};
|
||||
signal_spot probe;
|
||||
if (false == spot_on(*item, 1, 1, probe))
|
||||
{
|
||||
WriteLog("SELFTEST signalmove: no such track " + values["track"]);
|
||||
continue;
|
||||
}
|
||||
auto const facing{glm::dot(probe.travel, travel) >= 0.0 ? 1 : -1};
|
||||
auto const sideoftravel{glm::dot(geometry::plan_of(standing->include.location) - geometry::plan_of(standing->spot.axis), glm::dvec2{-travel.y, travel.x}) >= 0.0 ? 1 : -1};
|
||||
signal_select(standing->include.instance);
|
||||
tool.moving = true;
|
||||
tool.moved_valid = spot_on(*item, facing, sideoftravel * facing, tool.moved);
|
||||
tool.pressed = {-1000.f, -1000.f};
|
||||
signal_release();
|
||||
WriteLog("SELFTEST signalmove: " + tool.status);
|
||||
}
|
||||
else if (command == "signaldelete")
|
||||
{
|
||||
auto const *standing{standing_named(values["name"])};
|
||||
auto const deleted{standing != nullptr && signal_delete(standing->include.instance)};
|
||||
WriteLog(std::string{"SELFTEST signaldelete "} + values["name"] + (deleted ? ": removed with its event" : ": not removed by the signal tool"));
|
||||
}
|
||||
else if (command == "signalrename")
|
||||
{
|
||||
auto const *standing{standing_named(values["name"])};
|
||||
if (standing == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST signalrename: no signal " + values["name"]);
|
||||
continue;
|
||||
}
|
||||
select_include(standing->include.instance);
|
||||
m_include.values[0] = values["to"];
|
||||
apply_include();
|
||||
m_include_gesture = false;
|
||||
WriteLog("SELFTEST signalrename " + values["name"] + " -> " + values["to"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
signal_refresh(true);
|
||||
WriteLog(format("SELFTEST signalprobe: %zu signals", tool.standing.size()));
|
||||
for (auto const &standing : tool.standing)
|
||||
{
|
||||
std::string where{"not read"};
|
||||
if (standing.track != nullptr)
|
||||
{
|
||||
auto const &curve{standing.track->m_paths.front()};
|
||||
geometry::bezier const bez{curve};
|
||||
auto best{std::numeric_limits<double>::max()};
|
||||
double parameter{0.0};
|
||||
for (int k = 0; k <= 1024; ++k)
|
||||
{
|
||||
auto const d{geometry::plan_distance(bez.point(k / 1024.0), standing.include.location)};
|
||||
if (d < best)
|
||||
{
|
||||
best = d;
|
||||
parameter = k / 1024.0;
|
||||
}
|
||||
}
|
||||
auto const axis{bez.point(parameter)};
|
||||
auto tangent{bez.first(parameter)};
|
||||
glm::dvec2 travel{tangent.x, tangent.z};
|
||||
travel = glm::normalize(travel) * (standing.list == 1 ? -1.0 : 1.0);
|
||||
glm::dvec2 const offset{standing.include.location.x - axis.x, standing.include.location.z - axis.z};
|
||||
auto const cross{travel.x * offset.y - travel.y * offset.x};
|
||||
auto heading{glm::degrees(std::atan2(travel.x, travel.y))};
|
||||
auto turn{std::fmod(standing.include.yaw - heading + 720.0, 360.0)};
|
||||
auto const &list{editor_track::events(*standing.track, standing.list)};
|
||||
auto const bound{std::any_of(list.begin(), list.end(), [&](auto const &Event) { return Event.second != nullptr && ToLower(Event.first) == ToLower(standing.read); })};
|
||||
where = std::string{bound ? "bound, " : "unbound, "} + format("%s of %s, %.3f m from the axis, %s of the trains, %.3f m over the axis, model turned %.1f deg from the travel", editor_track::event_keyword(standing.list), standing.track->name().c_str(), glm::length(offset), cross > 0.0 ? "right" : "left",
|
||||
standing.include.location.y - axis.y, turn);
|
||||
}
|
||||
WriteLog(format("SELFTEST signalprobe %s %s at %.3f %.3f %.3f yaw %.2f read %s: %s", standing.name.c_str(), standing.include.file.c_str(), standing.include.location.x, standing.include.location.y, standing.include.location.z, standing.include.yaw,
|
||||
standing.read.empty() ? "-" : standing.read.c_str(), where.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (command == "quit")
|
||||
{
|
||||
if (false == test.quitting)
|
||||
|
||||
1018
application/editormodesignal.cpp
Normal file
1018
application/editormodesignal.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -561,15 +561,16 @@ void editor_mode::restore_track_snapshot(EditorSnapshot const &Snapshot, std::ve
|
||||
profile_restore();
|
||||
}
|
||||
|
||||
std::array<editor_mode::track_mode, 11> const &editor_mode::track_modes()
|
||||
std::array<editor_mode::track_mode, 12> const &editor_mode::track_modes()
|
||||
{
|
||||
static std::array<track_mode, 11> const modes{{
|
||||
static std::array<track_mode, 12> const modes{{
|
||||
{"Select", "Esc", track_tab::path, "LMB on a path selects it: its points, control vectors and parameters"},
|
||||
{"Lay track", "L", track_tab::lay, "Lays new track through the clicked points, on the ground or from a free end of a track"},
|
||||
{"Switch", "T", track_tab::turnout, "Puts switches into the selected straight or curve, edits the geometry of the selected switch"},
|
||||
{"Turntable", "U", track_tab::turntable, "Places a turntable, leads tracks out of it and fits the tracks around to it"},
|
||||
{"Straight", "G", track_tab::straights, "The whole straight through the selected path: drag its ends or the middle, break it, shift it"},
|
||||
{"Curve", "C", track_tab::route, "The line through the path, switch to switch: vertices with the radii, transitions and cant of the curves"},
|
||||
{"Signals", "H", track_tab::signals, "Signals by the track, from the templates of the scenery, with the event the train reads them by"},
|
||||
{"Objects", "B", track_tab::lineside, "Along the track: hectometre posts, fouling point markers, a parallel track, a vehicle to drive"},
|
||||
{"Profile", "P", track_tab::profile, "Vertical profile (grade line) along the line"},
|
||||
{"Speed", "V", track_tab::speed, "Speed limits of the paths against the speed their geometry allows"},
|
||||
@@ -636,6 +637,10 @@ void editor_mode::show_track_tab(track_tab const Tab)
|
||||
scan_turntables();
|
||||
turntable_read(track);
|
||||
break;
|
||||
case track_tab::signals:
|
||||
signal_scan();
|
||||
signal_refresh(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -676,7 +681,7 @@ bool editor_mode::track_shortcut(int const Key)
|
||||
bool editor_mode::track_busy() const
|
||||
{
|
||||
auto const &straights{m_straights};
|
||||
return (m_lay.active && false == m_lay.points.empty()) || m_turntable.placing || straights.dragging || m_extend.active || m_point_drag.active || m_handle_drag.active || m_switch.placing || m_track_point.valid() || m_route.vertex >= 0 || m_route.grip >= 0 || false == straights.detour.empty() || straights.tool != 0 || straights.handle >= 0;
|
||||
return (m_lay.active && false == m_lay.points.empty()) || m_turntable.placing || m_signal.placing || m_signal.moving || straights.dragging || m_extend.active || m_point_drag.active || m_handle_drag.active || m_switch.placing || m_track_point.valid() || m_route.vertex >= 0 || m_route.grip >= 0 || false == straights.detour.empty() || straights.tool != 0 || straights.handle >= 0;
|
||||
}
|
||||
|
||||
std::string editor_mode::track_mode_name() const
|
||||
@@ -707,6 +712,12 @@ std::string editor_mode::track_mode_name() const
|
||||
case track_tab::lineside:
|
||||
return STR_C("Objects along the track");
|
||||
case track_tab::infra: return STR_C("Infrastructure along the track");
|
||||
case track_tab::signals:
|
||||
if (m_signal.moving)
|
||||
return STR_C("Signal: drag along the track, release to put it there");
|
||||
if (m_signal.placing)
|
||||
return STR_C("Signal: drag along the track sets the direction of the trains, release to place it");
|
||||
return signal_armed() != nullptr ? STR_C("Signal: LMB by the track places it on that side") : STR_C("Signal: choose its type below");
|
||||
case track_tab::turntable:
|
||||
if (m_turntable.placing)
|
||||
return STR_C("Turntable: drag to turn the bridge, release to place it");
|
||||
@@ -770,6 +781,7 @@ void editor_mode::render_track_inspector()
|
||||
case track_tab::speed: render_speed_body(); break;
|
||||
case track_tab::joints: render_joints_body(); break;
|
||||
case track_tab::lineside: render_lineside_ui(); break;
|
||||
case track_tab::signals: render_signal_ui(); break;
|
||||
case track_tab::infra: render_infra_body(); break;
|
||||
case track_tab::turntable: render_turntable_ui(); break;
|
||||
default:
|
||||
@@ -815,8 +827,8 @@ void editor_mode::render_track_modes(TTrack *Track)
|
||||
ImGui::SetTooltip("%s [%s]", STR_C(mode.tooltip), mode.key);
|
||||
}
|
||||
};
|
||||
row("Edit", 0, 6);
|
||||
row("Analysis", 6, static_cast<int>(modes.size()));
|
||||
row("Edit", 0, 7);
|
||||
row("Analysis", 7, static_cast<int>(modes.size()));
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox(STR_C("Structure gauge"), &m_gauge.open);
|
||||
if (ImGui::IsItemHovered())
|
||||
@@ -3007,6 +3019,8 @@ void editor_mode::cancel_track_tools()
|
||||
}
|
||||
m_extend.active = false;
|
||||
m_turntable.placing = false;
|
||||
m_signal.placing = false;
|
||||
m_signal.moving = false;
|
||||
m_handle_drag = {};
|
||||
if (m_point_drag.active)
|
||||
{
|
||||
@@ -3468,6 +3482,14 @@ std::vector<editor_mode::key_hint> editor_mode::track_key_hints(bool const All)
|
||||
hints = {{"LMB on a row", "show the path"}};
|
||||
else if (m_track_window_open && m_track_tab == track_tab::lineside)
|
||||
hints = {{"LMB on a track", "the line the objects go along"}};
|
||||
else if (m_track_window_open && m_track_tab == track_tab::signals && (m_signal.placing || m_signal.moving))
|
||||
hints = {{"Drag along", m_signal.moving ? "moves the signal along the track" : "the trains it faces run that way"}, {"Release", "done"}, {"Esc", "cancel"}};
|
||||
else if (m_track_window_open && m_track_tab == track_tab::signals)
|
||||
{
|
||||
hints = {{"LMB by a track", "places the signal on that side, facing the trains which have it on their right"}, {"Press+drag along", "the trains it faces run the way of the drag"}, {"LMB on a signal", "selects it"}, {"Drag a signal", "moves it along the track"}};
|
||||
if (m_signal.selected != 0)
|
||||
hints.push_back({"Del", "removes the selected signal with its event"});
|
||||
}
|
||||
else if (m_track_window_open && m_track_tab == track_tab::joints)
|
||||
hints = {{"LMB on a row", "show the place"}, {"Fix", "joins the ends, aligns them"}};
|
||||
else if (m_track_window_open && m_track_tab == track_tab::infra)
|
||||
|
||||
@@ -193,7 +193,7 @@ void editor_mode::draw_track_hover() const
|
||||
bool editor_mode::track_gesture_on() const
|
||||
{
|
||||
return mouseHold || m_track_set_using || m_track_box.active || m_input.mouse.button(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS || m_track_gizmo_using || m_route_gizmo_using || m_extend.active || m_switch.placing || m_straights.dragging || m_straights.tool_mouse ||
|
||||
m_point_drag.active || m_handle_drag.active || m_sweep.dragging || m_turntable.placing || ImGuizmo::IsUsing();
|
||||
m_point_drag.active || m_handle_drag.active || m_sweep.dragging || m_turntable.placing || m_signal.placing || m_signal.moving || ImGuizmo::IsUsing();
|
||||
}
|
||||
|
||||
editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
|
||||
@@ -232,6 +232,40 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
|
||||
}
|
||||
if (m_track_tab == track_tab::turntable)
|
||||
return turntable_intent(ground, shift);
|
||||
if (m_track_tab == track_tab::signals)
|
||||
{
|
||||
if (auto const hit{signal_hit()}; hit >= 0)
|
||||
{
|
||||
auto const &standing{m_signal.standing[hit]};
|
||||
intent.index = hit;
|
||||
intent.position = standing.include.location;
|
||||
intent.action = format(STR_C("Click: select the signal %s, drag: move it along the track"), standing.name.c_str());
|
||||
intent.what = kind::signal_move;
|
||||
return intent;
|
||||
}
|
||||
auto const *armed{signal_armed()};
|
||||
if (m_hover.track != nullptr && armed != nullptr)
|
||||
{
|
||||
signal_spot spot;
|
||||
if (signal_spot_at(*m_hover.track, m_hover.path, m_hover.point, cursor_level(m_hover.point.y), 0, 0, *armed, spot))
|
||||
{
|
||||
intent.track = m_hover.track;
|
||||
intent.path = m_hover.path;
|
||||
intent.position = spot.position;
|
||||
intent.action = signal_intent_label(*armed, spot);
|
||||
intent.what = kind::signal_place;
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
if (m_hover.track != nullptr)
|
||||
{
|
||||
intent.track = m_hover.track;
|
||||
intent.path = m_hover.path;
|
||||
intent.position = m_hover.point;
|
||||
return make(kind::select, "Choose the type of the signal in the Tracks window first");
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
if (m_track_tab == track_tab::path)
|
||||
{
|
||||
if (shift && control && m_hover.track != nullptr && rail(*m_hover.track))
|
||||
@@ -419,6 +453,8 @@ void editor_mode::track_press(track_intent const &Intent)
|
||||
case kind::turntable_place:
|
||||
case kind::turntable_exit:
|
||||
case kind::turntable_fit: turntable_press(Intent); return;
|
||||
case kind::signal_place:
|
||||
case kind::signal_move: signal_press(Intent); return;
|
||||
case kind::select:
|
||||
case kind::none:
|
||||
{
|
||||
|
||||
@@ -251,6 +251,14 @@ bool parse(template_text const &Text, include_info &Info, std::string &Error)
|
||||
Info.switch_hand = drive["hand"].as<std::string>("");
|
||||
Info.switch_drive = drive["drive"].as<std::string>("");
|
||||
}
|
||||
if (auto const signal{document["signal"]}; signal.IsDefined() && signal.IsMap())
|
||||
{
|
||||
Info.signal_kind = signal["kind"].as<std::string>("");
|
||||
Info.signal_mount = signal["mount"].as<std::string>("");
|
||||
Info.signal_lamps = signal["lamps"].as<std::string>("");
|
||||
Info.signal_lean = signal["lean"].as<std::string>("");
|
||||
Info.signal_read = signal["read"].as<std::string>("");
|
||||
}
|
||||
if (auto const table{document["turntable"]}; table.IsDefined() && table.IsMap())
|
||||
{
|
||||
Info.turntable_length = table["length"].as<double>(0.0);
|
||||
@@ -644,7 +652,27 @@ std::string compose_directive(std::string const &File, std::vector<std::string>
|
||||
return text + " end";
|
||||
}
|
||||
|
||||
std::string directive(std::string const &File, include_info const &Info, int const Parameters, glm::dvec3 const &Location, std::optional<float> Yaw, std::string const &Track, glm::dvec2 const &Tilt)
|
||||
std::string substitute(std::string const &Text, std::vector<std::string> const &Values)
|
||||
{
|
||||
std::string result;
|
||||
std::size_t length{0};
|
||||
for (std::size_t idx = 0; idx < Text.size();)
|
||||
{
|
||||
auto const id{placeholder(Text, idx, length)};
|
||||
if (id > 0 && id <= static_cast<int>(Values.size()))
|
||||
{
|
||||
result += Values[id - 1];
|
||||
idx += length;
|
||||
}
|
||||
else
|
||||
{
|
||||
result += Text[idx++];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string directive(std::string const &File, include_info const &Info, int const Parameters, glm::dvec3 const &Location, std::optional<float> Yaw, std::string const &Track, glm::dvec2 const &Tilt, std::map<std::string, std::string> const &Values)
|
||||
{
|
||||
std::vector<std::string> values;
|
||||
for (auto id = 1; id <= Parameters; ++id)
|
||||
@@ -652,7 +680,15 @@ std::string directive(std::string const &File, include_info const &Info, int con
|
||||
auto const lookup{std::find_if(std::begin(Info.parameters), std::end(Info.parameters), [=](include_parameter const &Parameter) { return Parameter.id == id; })};
|
||||
auto const role{lookup != std::end(Info.parameters) ? lookup->role : std::string{"free"}};
|
||||
auto value{lookup != std::end(Info.parameters) ? lookup->value : std::string{}};
|
||||
if (role == "pos.x")
|
||||
if (auto const given{Values.find("#" + std::to_string(id))}; given != Values.end())
|
||||
{
|
||||
value = given->second;
|
||||
}
|
||||
else if (auto const given{Values.find(role)}; given != Values.end())
|
||||
{
|
||||
value = given->second;
|
||||
}
|
||||
else if (role == "pos.x")
|
||||
{
|
||||
value = number(Location.x);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -39,6 +40,11 @@ struct include_info
|
||||
double turntable_length{0.0};
|
||||
int turntable_angles{0};
|
||||
int turntable_positions{0};
|
||||
std::string signal_kind;
|
||||
std::string signal_mount;
|
||||
std::string signal_lamps;
|
||||
std::string signal_lean;
|
||||
std::string signal_read;
|
||||
std::vector<include_parameter> parameters;
|
||||
|
||||
// description of specified parameter, created if there's none
|
||||
@@ -71,7 +77,8 @@ bool complete(include_info const &Info, int Parameters, std::string *Issue = nul
|
||||
// builds include directive which places specified template. Location and Yaw are the placement to pass through
|
||||
// the parameters with matching roles, in the coordinates of the place the directive goes to; without Yaw the
|
||||
// rotation parameters receive their default values
|
||||
std::string directive(std::string const &File, include_info const &Info, int Parameters, glm::dvec3 const &Location, std::optional<float> Yaw, std::string const &Track = {}, glm::dvec2 const &Tilt = glm::dvec2{0.0});
|
||||
std::string directive(std::string const &File, include_info const &Info, int Parameters, glm::dvec3 const &Location, std::optional<float> Yaw, std::string const &Track = {}, glm::dvec2 const &Tilt = glm::dvec2{0.0}, std::map<std::string, std::string> const &Values = {});
|
||||
std::string substitute(std::string const &Text, std::vector<std::string> const &Values);
|
||||
|
||||
// splits include directive into the name of the included file and the values of the parameters, both as they're
|
||||
// written. returns: false if the text isn't a complete include directive
|
||||
|
||||
@@ -94,6 +94,8 @@ bool editorSettings::load()
|
||||
m_orthophoto.in_scene = (value == "1");
|
||||
else if (key == "platform_edge")
|
||||
parse(value, m_platform_edge);
|
||||
else if (key == "signal_offset")
|
||||
parse(value, m_signal_offset);
|
||||
else if (key.rfind(origin_prefix, 0) == 0)
|
||||
{
|
||||
// northing;easting
|
||||
@@ -133,6 +135,7 @@ bool editorSettings::save()
|
||||
stream << "ortho_lift " << m_orthophoto.lift << "\n";
|
||||
stream << "ortho_in_scene " << (m_orthophoto.in_scene ? 1 : 0) << "\n";
|
||||
stream << "platform_edge " << m_platform_edge << "\n";
|
||||
stream << "signal_offset " << m_signal_offset << "\n";
|
||||
stream << std::fixed << std::setprecision(3);
|
||||
for (auto const &origin : m_orthophoto_origins)
|
||||
stream << origin.first << " " << origin.second.first << ";" << origin.second.second << "\n";
|
||||
|
||||
@@ -41,12 +41,15 @@ public:
|
||||
|
||||
double platform_edge() const { return m_platform_edge; }
|
||||
void platform_edge(double Distance) { m_platform_edge = Distance; }
|
||||
double signal_offset() const { return m_signal_offset; }
|
||||
void signal_offset(double Distance) { m_signal_offset = Distance; }
|
||||
|
||||
private:
|
||||
movement_scheme m_movement{movement_scheme::wsad};
|
||||
orthophoto_settings m_orthophoto;
|
||||
std::map<std::string, std::pair<double, double>> m_orthophoto_origins;
|
||||
double m_platform_edge{1.725};
|
||||
double m_signal_offset{2.25};
|
||||
};
|
||||
|
||||
// global editor settings instance
|
||||
|
||||
@@ -4403,3 +4403,193 @@ msgstr "1,650 m: najmniej dopuszczalne w eksploatacji"
|
||||
|
||||
msgid "edge of the platform from the axis"
|
||||
msgstr "krawędź peronu od osi toru"
|
||||
|
||||
# scenery editor: signals
|
||||
msgid "Signals"
|
||||
msgstr "Sygnalizatory"
|
||||
|
||||
msgid "Signals by the track, from the templates of the scenery, with the event the train reads them by"
|
||||
msgstr "Sygnalizatory przy torze, z szablonów scenerii, z eventem, którym pociąg je odczytuje"
|
||||
|
||||
msgid "Signal: drag along the track, release to put it there"
|
||||
msgstr "Sygnalizator: przeciągnij wzdłuż toru, puść, żeby go tam postawić"
|
||||
|
||||
msgid "Signal: drag along the track sets the direction of the trains, release to place it"
|
||||
msgstr "Sygnalizator: przeciąganie wzdłuż toru ustawia kierunek pociągów, puść, żeby go postawić"
|
||||
|
||||
msgid "Signal: LMB by the track places it on that side"
|
||||
msgstr "Sygnalizator: LPM przy torze stawia go po tej stronie"
|
||||
|
||||
msgid "Signal: choose its type below"
|
||||
msgstr "Sygnalizator: wybierz jego typ poniżej"
|
||||
|
||||
msgid "moves the signal along the track"
|
||||
msgstr "przesuwa sygnalizator wzdłuż toru"
|
||||
|
||||
msgid "the trains it faces run that way"
|
||||
msgstr "pociągi, do których jest zwrócony, jadą w tę stronę"
|
||||
|
||||
msgid "LMB by a track"
|
||||
msgstr "LPM przy torze"
|
||||
|
||||
msgid "places the signal on that side, facing the trains which have it on their right"
|
||||
msgstr "stawia sygnalizator po tej stronie, zwrócony do pociągów, które mają go po prawej"
|
||||
|
||||
msgid "Press+drag along"
|
||||
msgstr "Wciśnij i przeciągnij wzdłuż"
|
||||
|
||||
msgid "the trains it faces run the way of the drag"
|
||||
msgstr "pociągi, do których jest zwrócony, jadą w stronę przeciągnięcia"
|
||||
|
||||
msgid "LMB on a signal"
|
||||
msgstr "LPM na sygnalizatorze"
|
||||
|
||||
msgid "Drag a signal"
|
||||
msgstr "Przeciągnij sygnalizator"
|
||||
|
||||
msgid "moves it along the track"
|
||||
msgstr "przesuwa go wzdłuż toru"
|
||||
|
||||
msgid "removes the selected signal with its event"
|
||||
msgstr "usuwa zaznaczony sygnalizator razem z jego eventem"
|
||||
|
||||
msgid "Click: select the signal %s, drag: move it along the track"
|
||||
msgstr "Klik: zaznacz sygnalizator %s, przeciągnij: przesuń go wzdłuż toru"
|
||||
|
||||
msgid "Choose the type of the signal in the Tracks window first"
|
||||
msgstr "Najpierw wybierz typ sygnalizatora w oknie Tory"
|
||||
|
||||
msgid "Semaphore"
|
||||
msgstr "Semafor"
|
||||
|
||||
msgid "Automatic block"
|
||||
msgstr "SBL"
|
||||
|
||||
msgid "Warning signal"
|
||||
msgstr "Tarcza ostrzegawcza"
|
||||
|
||||
msgid "Shunting signal"
|
||||
msgstr "Tarcza manewrowa"
|
||||
|
||||
msgid "Stop signal"
|
||||
msgstr "Zaporowy"
|
||||
|
||||
msgid "Substitute signal"
|
||||
msgstr "Sygnał zastępczy"
|
||||
|
||||
msgid "Repeater"
|
||||
msgstr "Powtarzacz"
|
||||
|
||||
msgid " on the right of the trains"
|
||||
msgstr " po prawej pociągów"
|
||||
|
||||
msgid " on the left of the trains"
|
||||
msgstr " po lewej pociągów"
|
||||
|
||||
msgid "Click: %s %s (%s)%s, the train doesn't read it"
|
||||
msgstr "Klik: %s %s (%s)%s, pociąg go nie odczytuje"
|
||||
|
||||
msgid "Drag along: the trains it faces run the way of the drag"
|
||||
msgstr "Przeciągnij wzdłuż: pociągi, do których jest zwrócony, jadą w stronę przeciągnięcia"
|
||||
|
||||
msgid "Click: %s %s (%s)%s, read by %s %s of %s"
|
||||
msgstr "Klik: %s %s (%s)%s, pociąg odczytuje go przez %s %s toru %s"
|
||||
|
||||
msgid "Signal %s moved"
|
||||
msgstr "Sygnalizator %s przesunięty"
|
||||
|
||||
msgid "Signal %s moved, read by %s %s of %s"
|
||||
msgstr "Sygnalizator %s przesunięty, pociąg odczytuje go przez %s %s toru %s"
|
||||
|
||||
msgid "Give the signal a name without spaces first"
|
||||
msgstr "Najpierw nadaj sygnalizatorowi nazwę bez spacji"
|
||||
|
||||
msgid "The signal couldn't be placed: "
|
||||
msgstr "Nie udało się postawić sygnalizatora: "
|
||||
|
||||
msgid "Signal %s placed (%s), the train doesn't read it"
|
||||
msgstr "Sygnalizator %s postawiony (%s), pociąg go nie odczytuje"
|
||||
|
||||
msgid "Signal %s placed (%s), but vehicles stand on the path: add %s to its %s by hand"
|
||||
msgstr "Sygnalizator %s postawiony (%s), ale na torze stoją pojazdy: dopisz %s do jego %s ręcznie"
|
||||
|
||||
msgid "Signal %s placed (%s), but the path can't take its event: %s"
|
||||
msgstr "Sygnalizator %s postawiony (%s), ale tor nie przyjmie jego eventu: %s"
|
||||
|
||||
msgid "Signal %s placed (%s), read by %s %s of %s. Its logic works after the scenery is saved and opened again"
|
||||
msgstr "Sygnalizator %s postawiony (%s), pociąg odczytuje go przez %s %s toru %s. Jego logika zadziała po zapisaniu i ponownym otwarciu scenerii"
|
||||
|
||||
msgid "Signal %s removed with its event %s, Ctrl+Z brings both back"
|
||||
msgstr "Sygnalizator %s usunięty razem z eventem %s, Ctrl+Z przywraca oba"
|
||||
|
||||
msgid "filter: name, w24, sbl..."
|
||||
msgstr "filtr: nazwa, w24, sbl..."
|
||||
|
||||
msgid "No template of this kind matches the filter"
|
||||
msgstr "Żaden szablon tego rodzaju nie pasuje do filtra"
|
||||
|
||||
msgid "%zu variants, e.g. %s"
|
||||
msgstr "wariantów: %zu, np. %s"
|
||||
|
||||
msgid "The train doesn't read it"
|
||||
msgstr "Pociąg go nie odczytuje"
|
||||
|
||||
msgid "The train reads it by an event of the path"
|
||||
msgstr "Pociąg odczytuje go przez event toru"
|
||||
|
||||
msgid "Variant of the chosen type. A head leaning to one side is swapped for its counterpart leaning towards the track on the other side"
|
||||
msgstr "Wariant wybranego typu. Głowica odchylona w jedną stronę zostaje zamieniona na odpowiednik odchylony w stronę toru, gdy sygnalizator stanie po drugiej stronie"
|
||||
|
||||
msgid "The train doesn't read it: it repeats what the signal ahead of it shows"
|
||||
msgstr "Pociąg go nie odczytuje: powtarza to, co pokazuje sygnalizator przed nim"
|
||||
|
||||
msgid "The train reads it by %s on the path it stands by"
|
||||
msgstr "Pociąg odczytuje go przez %s na torze, przy którym stoi"
|
||||
|
||||
msgid "Selected: %s (%s)"
|
||||
msgstr "Zaznaczony: %s (%s)"
|
||||
|
||||
msgid "Read by %s %s of %s"
|
||||
msgstr "Odczytywany przez %s %s toru %s"
|
||||
|
||||
msgid "No path has %s among its events: the train doesn't see it"
|
||||
msgstr "Żaden tor nie ma %s wśród swoich eventów: pociąg go nie widzi"
|
||||
|
||||
msgid "Name, plate and the linked signal: in the node properties. Del removes it with its event"
|
||||
msgstr "Nazwa, tablica i powiązany sygnalizator: we właściwościach węzła. Del usuwa go razem z eventem"
|
||||
|
||||
msgid "No signal templates: no template of the scenery directory is described with category: signal. tools/editor/signal_headers.py writes the descriptions into the signal templates."
|
||||
msgstr "Brak szablonów sygnalizatorów: żaden szablon katalogu scenerii nie ma opisu z category: signal. tools/editor/signal_headers.py dopisuje opisy do szablonów sygnalizatorów."
|
||||
|
||||
msgid "Name of the next signal; it counts up after each one placed. Its events and memory cell are named after it"
|
||||
msgstr "Nazwa następnego sygnalizatora; po każdym postawionym rośnie o jeden. Od niej biorą nazwy jego eventy i komórka pamięci"
|
||||
|
||||
msgid "auto: %s"
|
||||
msgstr "auto: %s"
|
||||
|
||||
msgid "Plate"
|
||||
msgstr "Tablica"
|
||||
|
||||
msgid "Texture of the plate from textures/tabl, e.g. a-12 or tob. Left empty: the one named like the signal, if there is one"
|
||||
msgstr "Tekstura tablicy z textures/tabl, np. a-12 albo tob. Puste: ta nazwana tak jak sygnalizator, jeśli istnieje"
|
||||
|
||||
msgid "no such texture"
|
||||
msgstr "nie ma takiej tekstury"
|
||||
|
||||
msgid "Linked signal"
|
||||
msgstr "Powiązany sygnalizator"
|
||||
|
||||
msgid "(p7) of the template: the warning signal a semaphore drives, the previous block signal, \"none\" for no link"
|
||||
msgstr "(p7) szablonu: tarcza ostrzegawcza, którą steruje semafor, poprzedni semafor SBL, \"none\": bez powiązania"
|
||||
|
||||
msgid "Distance of the mast from the axis of the track. 2.25 m is what the signals of the sceneries stand at"
|
||||
msgstr "Odległość masztu od osi toru. Na 2,25 m stoją sygnalizatory w sceneriach"
|
||||
|
||||
msgid "Over the rails (m)"
|
||||
msgstr "Nad szynami (m)"
|
||||
|
||||
msgid "A head for a gantry hangs over the axis of the track this high over the rail heads"
|
||||
msgstr "Głowica na bramkę wisi nad osią toru tak wysoko nad główkami szyn"
|
||||
|
||||
msgid " not read"
|
||||
msgstr " nieodczytywany"
|
||||
|
||||
237
tools/editor/signal_headers.py
Normal file
237
tools/editor/signal_headers.py
Normal file
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Writes //$e descriptions into the signal templates of the scenery directory, so the editor can place them.
|
||||
|
||||
usage: signal_headers.py <data root> [--write]
|
||||
without --write it only reports what it would write
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tarfile
|
||||
import time
|
||||
|
||||
FAMILIES = [
|
||||
(re.compile(r'^ss[dow]?\d'), 'main'),
|
||||
(re.compile(r'^sk(\d|-un)'), 'main'),
|
||||
(re.compile(r'^sbl\d'), 'block'),
|
||||
(re.compile(r'^(ts\d|tk\d|top|tsosp)'), 'warning'),
|
||||
(re.compile(r'^(ps\d|sp3)'), 'repeater'),
|
||||
(re.compile(r'^(ms\d|zs2nb)'), 'shunt'),
|
||||
(re.compile(r'^(zs1c|zs2cb|tzm|z1)'), 'stop'),
|
||||
(re.compile(r'^sz(i|k|y|yn|-ksztalt\d?)\.inc$'), 'substitute'),
|
||||
]
|
||||
|
||||
COLOURS = [('zielon', 'z'), ('czerwon', 'c'), ('pomara', 'p'), ('żółt', 'p'), ('biał', 'b'), ('mleczn', 'b'), ('niebiesk', 'n'), ('ślep', 'x'), ('brak', 'x')]
|
||||
|
||||
LINEMARK = '//$e'
|
||||
|
||||
|
||||
def tokens(text):
|
||||
text = re.sub(r'//[^\n]*', ' ', text)
|
||||
text = re.sub(r'/\*.*?\*/', ' ', text, flags=re.S)
|
||||
return re.split(r'[\s;]+', text.lower())
|
||||
|
||||
|
||||
def statements(words, keyword):
|
||||
out = []
|
||||
idx = 0
|
||||
while idx < len(words):
|
||||
if words[idx] == keyword:
|
||||
if keyword == 'event':
|
||||
stop = idx
|
||||
while stop < len(words) and words[stop] != 'endevent':
|
||||
stop += 1
|
||||
else:
|
||||
stop = idx + 10
|
||||
out.append(words[idx:stop])
|
||||
idx += 1
|
||||
return out
|
||||
|
||||
|
||||
def examine(path, file):
|
||||
raw = open(path, 'rb').read()
|
||||
try:
|
||||
text = raw.decode('utf-8')
|
||||
encoding = 'utf-8'
|
||||
except UnicodeDecodeError:
|
||||
text = raw.decode('cp1250')
|
||||
encoding = 'cp1250'
|
||||
lines = [line for line in text.splitlines() if not line.startswith(LINEMARK)]
|
||||
body = '\n'.join(lines)
|
||||
words = tokens(body)
|
||||
count = max([int(m) for m in re.findall(r'\(p(\d+)\)', body)] or [0])
|
||||
|
||||
if 'origin' not in words or words[words.index('origin') + 1:words.index('origin') + 4] != ['(p2)', '(p3)', '(p4)']:
|
||||
return None, 'no origin (p2) (p3) (p4)'
|
||||
if 'rotate' not in words or words[words.index('rotate') + 1:words.index('rotate') + 4] != ['0', '(p5)', '0']:
|
||||
return None, 'no rotate 0 (p5) 0'
|
||||
if count < 5:
|
||||
return None, 'fewer than 5 parameters'
|
||||
|
||||
models = [n for n in statements(words, 'node') if len(n) > 9 and n[4] == 'model']
|
||||
head = next((n for n in models if n[3].startswith('(p1)')), None)
|
||||
if head is None:
|
||||
return None, 'no model named after (p1)'
|
||||
model = head[9]
|
||||
|
||||
cells = {}
|
||||
for n in statements(words, 'node'):
|
||||
if len(n) > 8 and n[4] == 'memcell':
|
||||
cells[n[3]] = n[8]
|
||||
read = ''
|
||||
for e in statements(words, 'event'):
|
||||
velocity = (len(e) > 4 and e[2] == 'getvalues' and cells.get(e[4]) in ('setvelocity', 'shuntvelocity')) or (len(e) > 8 and e[2] == 'putvalues' and e[8] in ('setvelocity', 'shuntvelocity'))
|
||||
if velocity and e[1].startswith('(p1)'):
|
||||
if not read or e[1].endswith('_sem_info'):
|
||||
read = e[1]
|
||||
|
||||
kind = next((k for pattern, k in FAMILIES if pattern.search(file)), None)
|
||||
if kind is None:
|
||||
return None, 'not a signal family'
|
||||
if kind == 'repeater' and cells:
|
||||
return None, 'repeater with a memory cell'
|
||||
if kind in ('main', 'block', 'shunt', 'stop', 'substitute') and not read and cells:
|
||||
return None, 'memory cell which no event reads for the train'
|
||||
if kind in ('warning', 'repeater'):
|
||||
read = ''
|
||||
|
||||
models_used = [n[9] for n in models]
|
||||
lamps = ''
|
||||
lean = ''
|
||||
m = re.match(r'sem/(?:[a-z]+)_(\d)_([lpyk])_([a-z]+)[_.]', model)
|
||||
if m:
|
||||
lamps = m.group(3)
|
||||
lean = {'l': 'left', 'y': 'right', 'p': '', 'k': ''}[m.group(2)]
|
||||
else:
|
||||
m = re.match(r'sem/karzelki/k(?:[a-z]*?\d|tm|to|s)([zcpbnx]+)[.+]', model) or re.match(r'sem/glowice/(?:s|to)\d(?:i|y[lp])([zcpbnx]+)_dd\.', model)
|
||||
if m:
|
||||
lamps = m.group(1)
|
||||
if not lamps:
|
||||
for line in lines:
|
||||
if line.lower().lstrip('/ ').startswith('komory:'):
|
||||
for word in line.split(':', 1)[1].split(','):
|
||||
word = word.strip().lower()
|
||||
lamps += next((c for prefix, c in COLOURS if word.startswith(prefix)), '')
|
||||
break
|
||||
|
||||
if 'karzelki/' in model or re.search(r'k(w\d+)?(\+.*)?\.inc$', file) or 'pod_betonowa' in body:
|
||||
mount = 'dwarf'
|
||||
elif '_drb_' in model and not any(('slupy/' in m or 'post' in m or 'drabinki/' in m) for m in models_used):
|
||||
mount = 'gantry'
|
||||
else:
|
||||
mount = 'mast'
|
||||
|
||||
description = ''
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if stripped.startswith('//'):
|
||||
stripped = stripped.lstrip('/').strip()
|
||||
if stripped.lower().startswith('typ:'):
|
||||
stripped = stripped[4:].strip()
|
||||
if stripped:
|
||||
description = stripped
|
||||
break
|
||||
elif stripped:
|
||||
break
|
||||
|
||||
said = description.lower()
|
||||
if 'w lewo' in said:
|
||||
lean = 'left'
|
||||
elif 'w prawo' in said:
|
||||
lean = 'right'
|
||||
elif 'prost' in said:
|
||||
lean = ''
|
||||
|
||||
params = [
|
||||
"{id: 1, role: name, label: Name, default: A}",
|
||||
"{id: 2, role: pos.x}",
|
||||
"{id: 3, role: pos.y}",
|
||||
"{id: 4, role: pos.z}",
|
||||
"{id: 5, role: rot.y}",
|
||||
]
|
||||
if count >= 6:
|
||||
params.append("{id: 6, role: texture, label: Plate, default: 0_nothing}" if 'tabl/(p6)' in body.lower() else "{id: 6, role: text, label: Symbol, default: none}")
|
||||
for id in range(7, count + 1):
|
||||
label = 'Linked signal' if id == 7 else 'Parameter %d' % id
|
||||
params.append("{id: %d, role: text, label: %s, default: none}" % (id, label))
|
||||
|
||||
signal = ['kind: ' + kind, 'mount: ' + mount]
|
||||
if lamps:
|
||||
signal.append('lamps: ' + lamps)
|
||||
if lean:
|
||||
signal.append('lean: ' + lean)
|
||||
if read:
|
||||
signal.append('read: "' + read + '"')
|
||||
header = [
|
||||
'version: 1',
|
||||
'name: ' + file[:-4],
|
||||
'category: signal',
|
||||
]
|
||||
if description:
|
||||
header.append('description: "' + description.replace('\\', '\\\\').replace('"', '\\"') + '"')
|
||||
header.append('signal: {' + ', '.join(signal) + '}')
|
||||
header.append('params:')
|
||||
header += [' - ' + p for p in params]
|
||||
return {'raw': raw, 'text': text, 'encoding': encoding, 'header': header, 'kind': kind, 'model': model, 'read': read, 'lamps': lamps, 'mount': mount, 'lean': lean, 'count': count}, ''
|
||||
|
||||
|
||||
def rewrite(info):
|
||||
text = info['text']
|
||||
eol = '\r\n' if '\r\n' in text else '\n'
|
||||
bom = text.startswith('')
|
||||
if bom:
|
||||
text = text[1:]
|
||||
kept = [line for line in text.split(eol) if not line.startswith(LINEMARK)]
|
||||
block = [LINEMARK + ' ' + line for line in info['header']]
|
||||
out = eol.join(block + kept)
|
||||
return (('' if bom else '') + out).encode(info['encoding'])
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__)
|
||||
return 1
|
||||
root = os.path.join(sys.argv[1], 'scenery')
|
||||
write = '--write' in sys.argv
|
||||
files = sorted(f for f in os.listdir(root) if f.endswith('.inc'))
|
||||
files += ['pkp/semafory/' + f for f in sorted(os.listdir(os.path.join(root, 'pkp', 'semafory'))) if f.endswith('.inc')]
|
||||
done = []
|
||||
skipped = []
|
||||
for file in files:
|
||||
name = os.path.basename(file).lower()
|
||||
if not any(pattern.search(name) for pattern, _ in FAMILIES):
|
||||
continue
|
||||
info, reason = examine(os.path.join(root, file), name)
|
||||
if info is None:
|
||||
skipped.append((file, reason))
|
||||
continue
|
||||
done.append((file, info))
|
||||
|
||||
for file, info in done:
|
||||
print('%-34s %-10s %-6s %-6s %-6s p%d %-22s %s' % (file, info['kind'], info['mount'], info['lean'] or '-', info['lamps'] or '-', info['count'], info['read'] or '-', info['model']))
|
||||
for file, reason in skipped:
|
||||
print('SKIP %-34s %s' % (file, reason))
|
||||
kinds = {}
|
||||
for _, info in done:
|
||||
kinds[info['kind']] = kinds.get(info['kind'], 0) + 1
|
||||
print('described: %d %s, skipped: %d' % (len(done), kinds, len(skipped)))
|
||||
|
||||
if write:
|
||||
backup = os.path.join(sys.argv[1], 'editor_backup')
|
||||
os.makedirs(backup, exist_ok=True)
|
||||
archive = os.path.join(backup, time.strftime('signals_%Y%m%d_%H%M%S.tar'))
|
||||
with tarfile.open(archive, 'w') as tar:
|
||||
for file, _ in done:
|
||||
tar.add(os.path.join(root, file), arcname='scenery/' + file)
|
||||
for file, info in done:
|
||||
content = rewrite(info)
|
||||
if content != info['raw']:
|
||||
with open(os.path.join(root, file), 'wb') as out:
|
||||
out.write(content)
|
||||
print('written, originals in', archive)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user