17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 06:19:19 +02:00

feat(editor): more models selected at once (Shift+LMB), moved and turned by the gizmo together, undone in one step; Ctrl+C / Ctrl+V copy the selected models with their groups and paste them at the cursor; Ctrl+G groups the selection, Ctrl+Shift+G takes a group apart, groups written to the scenery as group ... endgroup and loaded as groups of their own; End drops each model of the selection and of their groups onto the ground; menu of the selection at the cursor on Ctrl+RMB; Edit menu with undo, redo, the clipboard, grouping, deleting; Help > Keyboard shortcuts (F1) with the keys of every field of work, F2-F5 choose the fields of work, the tracks menu lists the track tools with their keys, the keys and descriptions in the menus and tooltips; self-test commands for the selection; Polish translation

This commit is contained in:
2026-10-09 01:12:46 +02:00
parent 791d5d052c
commit f94100ffe7
14 changed files with 1354 additions and 78 deletions

View File

@@ -16,6 +16,7 @@ http://mozilla.org/MPL/2.0/.
#include "simulation/simulation.h"
#include "model/AnimModel.h"
#include "scene/scenenodegroups.h"
#include "world/MemCell.h"
#include "world/Traction.h"
#include "world/EvLaunch.h"
@@ -688,6 +689,11 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
{
return true;
}
if (Model != nullptr && Groups.relocating(Node))
{
// written anew along with its group, below
return true;
}
auto const location{Node->location()};
auto const angles{Model != nullptr ? Model->Angles() : glm::vec3{0.f}};
auto const scale{Model != nullptr ? Model->Scale() : glm::vec3{1.f}};
@@ -806,6 +812,87 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
return fail(state.error);
}
}
// models grouped or ungrouped in the editor: their definitions are taken out of where they are and written anew, a group
// as one block between group and endgroup in the layer of its first model, a model of a dissolved group on its own
std::set<basic_node const *> relocated;
auto const relocate = [&](TAnimModel const *Model, layer_handle const Target, std::vector<std::pair<basic_node const *, std::string>> &Output) {
if (auto const lookup{m_sources.find(Model)}; lookup != m_sources.end())
{
auto const &source{lookup->second};
if (false == writable(source.layer))
{
// one definition stands for more than one node, it stays where it is
return true;
}
if (false == load(source.layer))
{
return false;
}
file_patch::edit edit;
edit.begin = source.span.begin;
edit.end = source.span.end;
state.patches[source.layer].edits.emplace_back(std::move(edit));
rewritten.emplace(Model);
}
std::string text;
Model->export_as_text(text);
text.erase(text.find_last_not_of(" \t\r\n") + 1);
auto const &context{layer(Target).context_insert()};
if (false == context.matches(layer_context()))
{
model_placement placement;
placement.rotation = context.rotation;
placement.location = context.to_local(Model->location());
placement.angles = Model->Angles();
placement.scale = Model->Scale() / context.scale;
std::string error;
if (false == patch_model(text, placement, error))
{
state.error = error + " of \"" + Model->name() + "\"";
return false;
}
}
Output.emplace_back(Model, std::move(text));
relocated.emplace(Model);
return true;
};
for (auto *node : Groups.loose())
{
auto const *model{dynamic_cast<TAnimModel const *>(node)};
if (model == nullptr || model->from_template() || false == is_output(resolve(node->layer())))
{
continue;
}
if (false == relocate(model, resolve(node->layer()), created[resolve(node->layer())]))
{
return fail(state.error);
}
}
for (auto const group : Groups.rewritten())
{
auto const &nodes{Groups.group(group).nodes};
if (nodes.empty() || false == is_output(resolve(nodes.front()->layer())))
{
continue;
}
auto const target{resolve(nodes.front()->layer())};
auto &output{created[target]};
// NOTE: lines with no node are written as they are, without a place in the bookkeeping
output.emplace_back(nullptr, "group");
for (auto const *node : nodes)
{
auto const *model{dynamic_cast<TAnimModel const *>(node)};
if (model == nullptr || model->from_template())
{
continue;
}
if (false == relocate(model, target, output))
{
return fail(state.error);
}
}
output.emplace_back(nullptr, "endgroup");
}
auto const push_edit = [&](layer_handle const Layer, source_span const &Span, std::string Text, basic_node const *Node) {
file_patch::edit edit;
edit.begin = Span.begin;
@@ -1448,7 +1535,10 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
{
ensure_newline(added.text, eol);
auto const offset{static_cast<std::streamoff>(added.text.size())};
added.nodes.push_back({definition.first, {offset, offset + static_cast<std::streamoff>(definition.second.size())}, true});
if (definition.first != nullptr)
{
added.nodes.push_back({definition.first, {offset, offset + static_cast<std::streamoff>(definition.second.size())}, true});
}
added.text += definition.second + eol;
}
}
@@ -1620,6 +1710,7 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
}
// the scenery files now match the scene, update the bookkeeping to reflect it
Groups.saved();
auto removedterrain{false};
for (auto const output : outputs)
{
@@ -1629,7 +1720,8 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
auto const isnew{source.layer == null_handle};
source.layer = output;
source.span = place.span;
if (isnew)
// a definition written anew elsewhere is in the placement of the place it went to, as a new one is
if (isnew || relocated.count(place.node) != 0)
{
source.context = layer(output).context_insert();
}