diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index 795c7f969..97ef05677 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -63,18 +63,23 @@ QString NodeGroup::AddInputPassthrough(const NodeInput &input, const InputFlags Q_ASSERT(ContextContainsNode(input.node())); for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { - if (it.value() == input) { + if (it->second == input) { // Already passing this input through - return it.key(); + return it->first; } } // Add input - QString id = GetGroupInputIDFromInput(input); + QString id = input.input(); + int i = 2; + while (HasInputWithID(id)) { + id = QStringLiteral("%1_%2").arg(input.name(), QString::number(i)); + i++; + } AddInput(id, input.GetDataType(), input.GetDefaultValue(), input.GetFlags() | flags); - input_passthroughs_.insert(id, input); + input_passthroughs_.append({id, input}); emit InputPassthroughAdded(this, input); @@ -83,11 +88,11 @@ QString NodeGroup::AddInputPassthrough(const NodeInput &input, const InputFlags void NodeGroup::RemoveInputPassthrough(const NodeInput &input) { - for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { - if (it.value() == input) { - RemoveInput(it.key()); + for (auto it=input_passthroughs_.begin(); it!=input_passthroughs_.end(); it++) { + if (it->second == input) { + RemoveInput(it->first); + emit InputPassthroughRemoved(this, it->second); input_passthroughs_.erase(it); - emit InputPassthroughRemoved(this, it.value()); break; } } @@ -102,23 +107,10 @@ void NodeGroup::SetOutputPassthrough(Node *node) emit OutputPassthroughChanged(this, output_passthrough_); } -QString NodeGroup::GetGroupInputIDFromInput(const NodeInput &input) -{ - QCryptographicHash hash(QCryptographicHash::Sha1); - - hash.addData(input.node()->GetUUID().toByteArray()); - - hash.addData(input.input().toUtf8()); - - hash.addData((const char*) &input.element(), sizeof(input.element())); - - return QString::fromLatin1(hash.result().toHex()); -} - bool NodeGroup::ContainsInputPassthrough(const NodeInput &input) const { for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { - if (it.value() == input) { + if (it->second == input) { return true; } } @@ -135,7 +127,7 @@ QString NodeGroup::GetInputName(const QString &id) const } // Call GetInputName of passed through node, which may be another group - NodeInput pass = input_passthroughs_.value(id); + NodeInput pass = GetInputFromID(id); return pass.node()->GetInputName(pass.input()); } @@ -149,7 +141,7 @@ NodeInput NodeGroup::ResolveInput(NodeInput input) bool NodeGroup::GetInner(NodeInput *input) { if (NodeGroup *g = dynamic_cast(input->node())) { - const NodeInput &passthrough = g->GetInputPassthroughs().value(input->input()); + const NodeInput &passthrough = g->GetInputFromID(input->input()); input->set_node(passthrough.node()); input->set_input(passthrough.input()); return true; diff --git a/app/node/group/group.h b/app/node/group/group.h index 05e3b851a..b87339d30 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -52,9 +52,9 @@ public: void SetOutputPassthrough(Node *node); - static QString GetGroupInputIDFromInput(const NodeInput &input); - - const QHash &GetInputPassthroughs() const + using InputPassthrough = QPair; + using InputPassthroughs = QVector; + const InputPassthroughs &GetInputPassthroughs() const { return input_passthroughs_; } @@ -66,15 +66,35 @@ public: static NodeInput ResolveInput(NodeInput input); static bool GetInner(NodeInput *input); + QString GetIDOfPassthrough(const NodeInput &input) const + { + for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { + if (it->second == input) { + return it->first; + } + } + return QString(); + } + + NodeInput GetInputFromID(const QString &id) const + { + for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { + if (it->first == id) { + return it->second; + } + } + return NodeInput(); + } + signals: - void InputPassthroughAdded(NodeGroup *group, const NodeInput &input); + void InputPassthroughAdded(olive::NodeGroup *group, const olive::NodeInput &input); - void InputPassthroughRemoved(NodeGroup *group, const NodeInput &input); + void InputPassthroughRemoved(olive::NodeGroup *group, const olive::NodeInput &input); - void OutputPassthroughChanged(NodeGroup *group, Node *output); + void OutputPassthroughChanged(olive::NodeGroup *group, olive::Node *output); private: - QHash input_passthroughs_; + InputPassthroughs input_passthroughs_; Node *output_passthrough_; diff --git a/app/node/node.cpp b/app/node/node.cpp index dd4f98878..74468036e 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -50,7 +50,6 @@ Node::Node() : cache_result_(false), flags_(kNone) { - uuid_ = QUuid::createUuid(); } Node::~Node() @@ -991,12 +990,44 @@ Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap& cre // Add to map created.insert(node, copy); - // Copy values to the clone - CopyInputs(node, copy, false); - // Add it to the same graph command->add_child(new NodeAddCommand(node->parent(), copy)); + // Copy context children + const PositionMap &map = node->GetContextPositions(); + for (auto it=map.cbegin(); it!=map.cend(); it++) { + // Add either the copy (if it exists) or the original node to the context + Node *child; + + if (it.key()->IsItem()) { + child = it.key(); + } else { + child = created.value(it.key()); + if (!child) { + child = CopyNodeAndDependencyGraphMinusItemsInternal(created, it.key(), command); + } + } + + command->add_child(new NodeSetPositionCommand(child, copy, it.value())); + } + + // If this is a group, copy input and output passthroughs + if (NodeGroup *src_group = dynamic_cast(node)) { + NodeGroup *dst_group = static_cast(copy); + + for (auto it=src_group->GetInputPassthroughs().cbegin(); it!=src_group->GetInputPassthroughs().cend(); it++) { + // This node should have been created by the context loop above + NodeInput input = it->second; + input.set_node(created.value(input.node())); + command->add_child(new NodeGroupAddInputPassthrough(dst_group, input)); + } + + command->add_child(new NodeGroupSetOutputPassthrough(dst_group, created.value(src_group->GetOutputPassthrough()))); + } + + // Copy values to the clone + command->add_child(new NodeCopyInputsCommand(node, copy, false)); + // Go through input connections and copy if non-item and connect if item for (auto it=node->input_connections_.cbegin(); it!=node->input_connections_.cend(); it++) { NodeInput input = it->first; @@ -1009,23 +1040,17 @@ Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap& cre } else { // Non-item, we want to clone this too connected_copy = created.value(connected, nullptr); - if (!connected_copy) { connected_copy = CopyNodeAndDependencyGraphMinusItemsInternal(created, connected, command); } } - NodeInput copied_input(copy, input.input(), input.element()); + NodeInput copied_input = input; + copied_input.set_node(copy); command->add_child(new NodeEdgeAddCommand(connected_copy, copied_input)); command->add_child(new NodeSetValueHintCommand(copied_input, node->GetValueHintForInput(input.input(), input.element()))); } - const PositionMap &map = node->GetContextPositions(); - for (auto it=map.cbegin(); it!=map.cend(); it++) { - // Add either the copy (if it exists) or the original node to the context - command->add_child(new NodeSetPositionCommand(created.value(it.key(), it.key()), copy, it.value())); - } - return copy; } @@ -1045,8 +1070,7 @@ Node *Node::CopyNodeInGraph(Node *node, MultiUndoCommand *command) } else { copy = node->copy(); - command->add_child(new NodeAddCommand(static_cast(node->parent()), - copy)); + command->add_child(new NodeAddCommand(static_cast(node->parent()), copy)); command->add_child(new NodeCopyInputsCommand(node, copy, true)); @@ -1302,6 +1326,11 @@ void Node::CopyInputs(const Node *source, Node *destination, bool include_connec Q_ASSERT(source->id() == destination->id()); foreach (const QString& input, source->inputs()) { + // NOTE: This assert is to ensure that inputs in the source also exist in the destination, which + // they should. If they don't and you hit this assert, check if you're handling group + // passthroughs correctly. + Q_ASSERT(destination->HasInputWithID(input)); + CopyInput(source, destination, input, include_connections, true); } diff --git a/app/node/node.h b/app/node/node.h index 3268cc34e..f0c27c55a 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -27,7 +27,6 @@ #include #include #include -#include #include #include "codec/frame.h" @@ -121,9 +120,6 @@ public: Project* project() const; - const QUuid &GetUUID() const {return uuid_;} - void SetUUID(const QUuid &uuid) {uuid_ = uuid;} - const uint64_t &GetFlags() const { return flags_; @@ -1340,8 +1336,6 @@ private: PositionMap context_positions_; - QUuid uuid_; - uint64_t flags_; QVector gizmos_; diff --git a/app/node/project/serializer/serializer210528.cpp b/app/node/project/serializer/serializer210528.cpp index 403105ad8..6c9ec058f 100644 --- a/app/node/project/serializer/serializer210528.cpp +++ b/app/node/project/serializer/serializer210528.cpp @@ -178,8 +178,6 @@ void ProjectSerializer210528::LoadNode(Node *node, XMLNodeData &xml_node_data, Q xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), node); } else if (reader->name() == QStringLiteral("label")) { node->SetLabel(reader->readElementText()); - } else if (reader->name() == QStringLiteral("uuid")) { - node->SetUUID(QUuid::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("color")) { node->SetOverrideColor(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("links")) { diff --git a/app/node/project/serializer/serializer210907.cpp b/app/node/project/serializer/serializer210907.cpp index 688b7ad07..870fa0b30 100644 --- a/app/node/project/serializer/serializer210907.cpp +++ b/app/node/project/serializer/serializer210907.cpp @@ -178,8 +178,6 @@ void ProjectSerializer210907::LoadNode(Node *node, XMLNodeData &xml_node_data, Q xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), node); } else if (reader->name() == QStringLiteral("label")) { node->SetLabel(reader->readElementText()); - } else if (reader->name() == QStringLiteral("uuid")) { - node->SetUUID(QUuid::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("color")) { node->SetOverrideColor(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("links")) { diff --git a/app/node/project/serializer/serializer211228.cpp b/app/node/project/serializer/serializer211228.cpp index a10d34bae..e41c33360 100644 --- a/app/node/project/serializer/serializer211228.cpp +++ b/app/node/project/serializer/serializer211228.cpp @@ -303,7 +303,7 @@ void ProjectSerializer211228::LoadNode(Node *node, XMLNodeData &xml_node_data, Q } else if (reader->name() == QStringLiteral("label")) { node->SetLabel(reader->readElementText()); } else if (reader->name() == QStringLiteral("uuid")) { - node->SetUUID(QUuid::fromString(reader->readElementText())); + xml_node_data.node_uuids.insert(node, QUuid::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("color")) { node->SetOverrideColor(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("links")) { @@ -377,7 +377,6 @@ void ProjectSerializer211228::SaveNode(Node *node, QXmlStreamWriter *writer) con { writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast(node))); - writer->writeTextElement(QStringLiteral("uuid"), node->GetUUID().toString()); writer->writeTextElement(QStringLiteral("label"), node->GetLabel()); writer->writeTextElement(QStringLiteral("color"), QString::number(node->GetOverrideColor())); @@ -844,11 +843,11 @@ void ProjectSerializer211228::SaveNodeCustom(QXmlStreamWriter *writer, Node *nod } else if (NodeGroup *group = dynamic_cast(node)) { writer->writeStartElement(QStringLiteral("inputpassthroughs")); - foreach (const NodeInput &ip, group->GetInputPassthroughs()) { + foreach (const NodeGroup::InputPassthrough &ip, group->GetInputPassthroughs()) { writer->writeStartElement(QStringLiteral("inputpassthrough")); - writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast(ip.node()))); - writer->writeTextElement(QStringLiteral("input"), ip.input()); - writer->writeTextElement(QStringLiteral("element"), QString::number(ip.element())); + writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast(ip.second.node()))); + writer->writeTextElement(QStringLiteral("input"), ip.second.input()); + writer->writeTextElement(QStringLiteral("element"), QString::number(ip.second.element())); writer->writeEndElement(); // input } diff --git a/app/node/project/serializer/serializer211228.h b/app/node/project/serializer/serializer211228.h index 543a45498..a13e0641b 100644 --- a/app/node/project/serializer/serializer211228.h +++ b/app/node/project/serializer/serializer211228.h @@ -64,6 +64,7 @@ private: QList block_links; QVector group_input_links; QHash group_output_links; + QHash node_uuids; }; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 887c8b578..dcdb3d20a 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -378,9 +378,6 @@ void PreviewAutoCacher::AddNode(Node *node) // Add to project copy->setParent(&copied_project_); - // Copy UUID - copy->SetUUID(node->GetUUID()); - // Insert into map InsertIntoCopyMap(node, copy); @@ -420,6 +417,11 @@ void PreviewAutoCacher::RemoveEdge(Node *output, const NodeInput &input) void PreviewAutoCacher::CopyValue(const NodeInput &input) { + if (dynamic_cast(input.node())) { + // Group nodes are just dummy nodes, no need to copy them + return; + } + // Copy all values to our graph Node* our_input = copy_map_.value(input.node()); Node::CopyValuesOfElement(input.node(), our_input, input.input(), input.element()); @@ -427,6 +429,11 @@ void PreviewAutoCacher::CopyValue(const NodeInput &input) void PreviewAutoCacher::CopyValueHint(const NodeInput &input) { + if (dynamic_cast(input.node())) { + // Group nodes are just dummy nodes, no need to copy them + return; + } + // Copy value hint to our graph Node* our_input = copy_map_.value(input.node()); Node::ValueHint hint = input.node()->GetValueHintForInput(input.input(), input.element()); diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 5e334b3c0..92b8f0d03 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -213,20 +213,67 @@ void NodeView::Paste() void NodeView::Duplicate() { if (!selected_nodes_.isEmpty()) { - Node::PositionMap map; + QVector selected = selected_nodes_; QVector new_nodes; - new_nodes.resize(selected_nodes_.size()); + Node::PositionMap map; - for (int i=0; icopy(); - Node::CopyInputs(og, copy, false); - map.insert(copy, GetAssumedPositionForSelectedNode(og)); - new_nodes[i] = copy; + new_nodes.resize(selected.size()); + + // Create copies of each selected node, checking for groups and adding children if necessary + for (int i=0; icopy(); + + if (NodeGroup *g = dynamic_cast(selected.at(i))) { + for (auto it=g->GetContextPositions().cbegin(); it!=g->GetContextPositions().cend(); it++) { + if (!selected.contains(it.key())) { + // This should automatically recurse if this is a group inside a group + selected.append(it.key()); + } + } + new_nodes.resize(selected.size()); + } } - Node::CopyDependencyGraph(selected_nodes_, new_nodes, nullptr); + // Get positions in contexts, add input passthroughs, and copy input values/keyframes + for (int i=0; iGetContextPositions().cbegin(); it!=og->GetContextPositions().cend(); it++) { + Node *child_og = it.key(); + int child_index = selected.indexOf(child_og); + + if (child_index != -1) { + Node *child_copy = new_nodes.at(child_index); + + copy->SetNodePositionInContext(child_copy, it.value()); + } + } + + if (NodeGroup *src_group = dynamic_cast(og)) { + NodeGroup *dst_group = static_cast(copy); + + for (auto it=src_group->GetInputPassthroughs().cbegin(); it!=src_group->GetInputPassthroughs().cend(); it++) { + NodeInput input = it->second; + input.set_node(new_nodes.at(selected.indexOf(input.node()))); + dst_group->AddInputPassthrough(input); + } + + dst_group->SetOutputPassthrough(new_nodes.at(selected.indexOf(src_group->GetOutputPassthrough()))); + } + + Node::CopyInputs(selected.at(i), new_nodes.at(i), false); + } + + // Copy connections + Node::CopyDependencyGraph(selected, new_nodes, nullptr); + + // Set root level context positions and attach to PostPaste(new_nodes, map); } } @@ -1020,12 +1067,13 @@ NodeViewItem *NodeView::GetAssumedItemForSelectedNode(Node *node) return nullptr; } -Node::Position NodeView::GetAssumedPositionForSelectedNode(Node *node) +bool NodeView::GetAssumedPositionForSelectedNode(Node *node, Node::Position *pos) { if (NodeViewItem *item = GetAssumedItemForSelectedNode(node)) { - return item->GetNodePositionData(); + *pos = item->GetNodePositionData(); + return true; } else { - return Node::Position(); + return false; } } @@ -1353,7 +1401,7 @@ void NodeView::EndEdgeDrag(bool cancel) } while (NodeGroup *input_group = dynamic_cast(creating_input.node())) { - creating_input = input_group->GetInputPassthroughs().value(creating_input.input()); + creating_input = input_group->GetInputFromID(creating_input.input()); } if (creating_input.IsConnected()) { diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index b3d6d4038..79964a998 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -149,7 +149,7 @@ private: QPointF GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const; NodeViewItem *GetAssumedItemForSelectedNode(Node *node); - Node::Position GetAssumedPositionForSelectedNode(Node *node); + bool GetAssumedPositionForSelectedNode(Node *node, Node::Position *pos); Menu *CreateAddMenu(Menu *parent); diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 01c1c6eb6..479bd9f72 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -464,10 +464,16 @@ void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) { - if (change == ItemPositionHasChanged && node_) { - ReadjustAllEdges(); + if (node_) { + if (change == ItemPositionHasChanged) { + ReadjustAllEdges(); - UpdateContextRect(); + UpdateContextRect(); + } else if (change == ItemSelectedHasChanged) { + if (value.toBool()) { + qDebug() << "Selected node:" << node_; + } + } } return QGraphicsItem::itemChange(change, value); @@ -786,7 +792,7 @@ NodeViewItem *NodeViewItem::GetItemForInput(NodeInput input) if (NodeGroup *group = dynamic_cast(node_)) { if (input.node() != group) { // Translate input to group input - QString id = NodeGroup::GetGroupInputIDFromInput(input); + QString id = group->GetIDOfPassthrough(input); input.set_node(group); input.set_input(id); }