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:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user