mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-02 12:49:13 +01: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();
|
||||
}
|
||||
|
||||
@@ -22,9 +22,20 @@ node_groups Groups;
|
||||
|
||||
// requests creation of a new node group. returns: handle to the group
|
||||
scene::group_handle
|
||||
node_groups::create() {
|
||||
node_groups::create( bool const Separate ) {
|
||||
|
||||
m_activegroup.push( create_handle() );
|
||||
if( false == Separate ) {
|
||||
m_activegroup.push( create_handle() );
|
||||
return handle();
|
||||
}
|
||||
// a handle no other group has, see make()
|
||||
auto group { std::max<group_handle>( 2, static_cast<group_handle>( m_groupmap.size() + 1 ) ) };
|
||||
while( m_groupmap.find( group ) != m_groupmap.end() ) {
|
||||
++group;
|
||||
}
|
||||
// NOTE: registered before the nodes come, so that a group nested in this one doesn't get the same handle
|
||||
m_groupmap[ group ];
|
||||
m_activegroup.push( group );
|
||||
|
||||
return handle();
|
||||
}
|
||||
@@ -334,6 +345,87 @@ node_groups::erase( group_map::const_iterator Group ) {
|
||||
m_groupmap.erase( Group );
|
||||
}
|
||||
|
||||
group_handle
|
||||
node_groups::make( std::vector<scene::basic_node *> const &Nodes ) {
|
||||
|
||||
// a handle no other group has, past the first one, which the editor treats as no group at all
|
||||
auto handle { std::max<group_handle>( 2, static_cast<group_handle>( m_groupmap.size() + 1 ) ) };
|
||||
while( m_groupmap.find( handle ) != m_groupmap.end() ) {
|
||||
++handle;
|
||||
}
|
||||
for( auto *node : Nodes ) {
|
||||
leave( node );
|
||||
m_loose.erase( node );
|
||||
insert( handle, node );
|
||||
}
|
||||
m_rewrite.insert( handle );
|
||||
return handle;
|
||||
}
|
||||
|
||||
void
|
||||
node_groups::dissolve( scene::group_handle const Group ) {
|
||||
|
||||
auto const lookup { m_groupmap.find( Group ) };
|
||||
if( lookup == m_groupmap.end() ) { return; }
|
||||
for( auto *node : lookup->second.nodes ) {
|
||||
m_loose.insert( node );
|
||||
}
|
||||
m_rewrite.erase( Group );
|
||||
erase( lookup );
|
||||
}
|
||||
|
||||
void
|
||||
node_groups::remove( scene::basic_node *Node ) {
|
||||
|
||||
leave( Node );
|
||||
m_loose.erase( Node );
|
||||
}
|
||||
|
||||
void
|
||||
node_groups::leave( scene::basic_node *Node ) {
|
||||
|
||||
auto const group { Node->group() };
|
||||
Node->group( null_handle );
|
||||
auto const lookup { m_groupmap.find( group ) };
|
||||
if( lookup == m_groupmap.end() ) { return; }
|
||||
auto &nodes { lookup->second.nodes };
|
||||
nodes.erase( std::remove( std::begin( nodes ), std::end( nodes ), Node ), std::end( nodes ) );
|
||||
if( nodes.empty() && lookup->second.events.empty() ) {
|
||||
// a group made in the editor goes with its last node
|
||||
m_rewrite.erase( group );
|
||||
m_groupmap.erase( lookup );
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
node_groups::relocating( scene::basic_node const *Node ) const {
|
||||
|
||||
return ( m_loose.count( Node ) != 0 ) || ( m_rewrite.count( Node->group() ) != 0 );
|
||||
}
|
||||
|
||||
std::vector<scene::group_handle>
|
||||
node_groups::rewritten() const {
|
||||
|
||||
return { std::begin( m_rewrite ), std::end( m_rewrite ) };
|
||||
}
|
||||
|
||||
std::vector<scene::basic_node *>
|
||||
node_groups::loose() const {
|
||||
|
||||
std::vector<scene::basic_node *> nodes;
|
||||
for( auto const *node : m_loose ) {
|
||||
nodes.emplace_back( const_cast<scene::basic_node *>( node ) );
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
void
|
||||
node_groups::saved() {
|
||||
|
||||
m_rewrite.clear();
|
||||
m_loose.clear();
|
||||
}
|
||||
|
||||
// creates handle for a new group
|
||||
group_handle
|
||||
node_groups::create_handle() {
|
||||
|
||||
@@ -29,8 +29,9 @@ public:
|
||||
node_groups() = default;
|
||||
// methods
|
||||
// requests creation of a new node group. returns: handle to the group
|
||||
// nested groups are flattened into the outer one, unless Separate: then the group stands by itself (group ... endgroup)
|
||||
group_handle
|
||||
create();
|
||||
create( bool const Separate = false );
|
||||
// indicates creation of current group ended. returns: handle to the parent group or null_handle if group stack is empty
|
||||
group_handle
|
||||
close();
|
||||
@@ -50,6 +51,26 @@ public:
|
||||
scene::basic_group &
|
||||
group( scene::group_handle const Group ) {
|
||||
return m_groupmap[ Group ]; }
|
||||
// groups made in the scenery editor. the nodes leave the groups they were in. returns: handle to the new group
|
||||
group_handle
|
||||
make( std::vector<scene::basic_node *> const &Nodes );
|
||||
// the nodes of the group are left on their own
|
||||
void
|
||||
dissolve( scene::group_handle const Group );
|
||||
// the node, about to be deleted, leaves its group
|
||||
void
|
||||
remove( scene::basic_node *Node );
|
||||
// groups made or dissolved in the editor are written anew when the scenery is saved: the definitions of their nodes are
|
||||
// taken out of where they are, a group's written as one block between group and endgroup, a dissolved one's nodes each on its own
|
||||
bool
|
||||
relocating( scene::basic_node const *Node ) const;
|
||||
std::vector<scene::group_handle>
|
||||
rewritten() const;
|
||||
std::vector<scene::basic_node *>
|
||||
loose() const;
|
||||
// the scenery files match the groups now
|
||||
void
|
||||
saved();
|
||||
// sends basic content of the class in legacy (text) format to provided stream
|
||||
void
|
||||
export_as_text( std::ostream &Output, bool const Dirty ) const;
|
||||
@@ -66,9 +87,14 @@ private:
|
||||
create_handle();
|
||||
bool
|
||||
assign_cross_switch(map::track_switch&sw, std::string &sw_name, const std::string &id, size_t idx);
|
||||
// the node leaves the group it's in
|
||||
void
|
||||
leave( scene::basic_node *Node );
|
||||
// members
|
||||
group_map m_groupmap; // map of established node groups
|
||||
std::stack<scene::group_handle> m_activegroup; // helper, group to be assigned to newly created nodes
|
||||
std::set<scene::group_handle> m_rewrite; // groups made in the editor, to be written as blocks on save
|
||||
std::set<scene::basic_node const *> m_loose; // nodes of groups dissolved in the editor, to be written on their own on save
|
||||
};
|
||||
|
||||
extern node_groups Groups;
|
||||
|
||||
Reference in New Issue
Block a user