From fff39b0835510e01710b30ce45fe54d742ec5a25 Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Fri, 9 Nov 2018 11:20:48 +0800 Subject: [PATCH] Add multiple frames per pose support Usually one pose consists of one frame, however, sometimes multiple frames per one reference sheet for a serial of action could be very useful, such as a sprite sheet. Multiple frames per pose is different with one motion, one motion could contains multiple poses. Currently, the duration of one frame is fixed to 0.042s, it's based on the 24 frames per second calculation. --- src/document.cpp | 55 +++++----- src/document.h | 18 ++-- src/documentwindow.cpp | 4 +- src/materialeditwidget.cpp | 2 +- src/motioneditwidget.cpp | 4 +- src/motionmanagewidget.cpp | 2 +- src/motionsgenerator.cpp | 65 ++++++++---- src/motionsgenerator.h | 10 +- src/posedocument.cpp | 77 ++++++++++++-- src/posedocument.h | 3 +- src/poseeditwidget.cpp | 177 ++++++++++++++++++++++++++------- src/poseeditwidget.h | 31 ++++-- src/posemanagewidget.cpp | 6 +- src/posepreviewsgenerator.cpp | 16 +-- src/posepreviewsgenerator.h | 12 +-- src/skeletongraphicswidget.cpp | 4 +- src/snapshot.h | 12 ++- src/snapshotxml.cpp | 50 +++++++--- 18 files changed, 394 insertions(+), 154 deletions(-) diff --git a/src/document.cpp b/src/document.cpp index ab56206a..5a9df559 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -307,14 +307,15 @@ QUuid Document::createNode(float x, float y, float z, float radius, QUuid fromNo return node.id; } -void Document::addPose(QString name, std::map> parameters) +void Document::addPose(QString name, std::vector, std::map>>> frames, QUuid turnaroundImageId) { QUuid newPoseId = QUuid::createUuid(); auto &pose = poseMap[newPoseId]; pose.id = newPoseId; pose.name = name; - pose.parameters = parameters; + pose.frames = frames; + pose.turnaroundImageId = turnaroundImageId; pose.dirty = true; poseIdList.push_back(newPoseId); @@ -402,31 +403,32 @@ void Document::removePose(QUuid poseId) emit optionsChanged(); } -void Document::setPoseParameters(QUuid poseId, std::map> parameters) +void Document::setPoseFrames(QUuid poseId, std::vector, std::map>>> frames) { auto findPoseResult = poseMap.find(poseId); if (findPoseResult == poseMap.end()) { qDebug() << "Find pose failed:" << poseId; return; } - findPoseResult->second.parameters = parameters; + findPoseResult->second.frames = frames; findPoseResult->second.dirty = true; emit posesChanged(); - emit poseParametersChanged(poseId); + emit poseFramesChanged(poseId); emit optionsChanged(); } -void Document::setPoseAttributes(QUuid poseId, std::map attributes) +void Document::setPoseTurnaroundImageId(QUuid poseId, QUuid imageId) { auto findPoseResult = poseMap.find(poseId); if (findPoseResult == poseMap.end()) { qDebug() << "Find pose failed:" << poseId; return; } - findPoseResult->second.attributes = attributes; + if (findPoseResult->second.turnaroundImageId == imageId) + return; + findPoseResult->second.turnaroundImageId = imageId; findPoseResult->second.dirty = true; - emit posesChanged(); - emit poseAttributesChanged(poseId); + emit poseTurnaroundImageIdChanged(poseId); emit optionsChanged(); } @@ -971,11 +973,13 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &limitNodeId continue; } auto &poseIt = *findPoseResult; - std::map pose = poseIt.second.attributes; + std::map pose; pose["id"] = poseIt.second.id.toString(); if (!poseIt.second.name.isEmpty()) pose["name"] = poseIt.second.name; - snapshot->poses.push_back(std::make_pair(pose, poseIt.second.parameters)); + if (!poseIt.second.turnaroundImageId.isNull()) + pose["canvasImageId"] = poseIt.second.turnaroundImageId.toString(); + snapshot->poses.push_back(std::make_pair(pose, poseIt.second.frames)); } } if (DocumentToSnapshotFor::Document == forWhat || @@ -1221,14 +1225,10 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste) newPose.id = newPoseId; const auto &poseAttributes = poseIt.first; newPose.name = valueOfKeyInMapOrEmpty(poseAttributes, "name"); - for (const auto &attribute: poseAttributes) { - if (attribute.first == "name" || - attribute.first == "id") { - continue; - } - newPose.attributes.insert({attribute.first, attribute.second}); - } - newPose.parameters = poseIt.second; + auto findCanvasImageId = poseAttributes.find("canvasImageId"); + if (findCanvasImageId != poseAttributes.end()) + newPose.turnaroundImageId = QUuid(findCanvasImageId->second); + newPose.frames = poseIt.second; oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(poseAttributes, "id"))] = newPoseId; poseIdList.push_back(newPoseId); emit poseAdded(newPoseId); @@ -2654,7 +2654,7 @@ void Document::generateMotions() m_motionsGenerator = new MotionsGenerator(rigType, rigBones, rigWeights, currentRiggedOutcome()); bool hasDirtyMotion = false; for (const auto &pose: poseMap) { - m_motionsGenerator->addPoseToLibrary(pose.first, pose.second.parameters); + m_motionsGenerator->addPoseToLibrary(pose.first, pose.second.frames); } for (auto &motion: motionMap) { if (motion.second.dirty) { @@ -2722,7 +2722,12 @@ void Document::generatePosePreviews() for (auto &poseIt: poseMap) { if (!poseIt.second.dirty) continue; - m_posePreviewsGenerator->addPose(poseIt.first, poseIt.second.parameters); + if (poseIt.second.frames.empty()) + continue; + int middle = poseIt.second.frames.size() / 2; + if (middle >= (int)poseIt.second.frames.size()) + middle = 0; + m_posePreviewsGenerator->addPose({poseIt.first, middle}, poseIt.second.frames[middle].second); poseIt.second.dirty = false; hasDirtyPose = true; } @@ -2745,12 +2750,12 @@ void Document::generatePosePreviews() void Document::posePreviewsReady() { - for (const auto &poseId: m_posePreviewsGenerator->generatedPreviewPoseIds()) { - auto pose = poseMap.find(poseId); + for (const auto &poseIdAndFrame: m_posePreviewsGenerator->generatedPreviewPoseIdAndFrames()) { + auto pose = poseMap.find(poseIdAndFrame.first); if (pose != poseMap.end()) { - MeshLoader *resultPartPreviewMesh = m_posePreviewsGenerator->takePreview(poseId); + MeshLoader *resultPartPreviewMesh = m_posePreviewsGenerator->takePreview(poseIdAndFrame); pose->second.updatePreviewMesh(resultPartPreviewMesh); - emit posePreviewChanged(poseId); + emit posePreviewChanged(poseIdAndFrame.first); } } diff --git a/src/document.h b/src/document.h index 70c7352c..2d44bdc6 100644 --- a/src/document.h +++ b/src/document.h @@ -195,8 +195,8 @@ public: QUuid id; QString name; bool dirty = true; - std::map attributes; - std::map> parameters; + QUuid turnaroundImageId; + std::vector, std::map>>> frames; // pair void updatePreviewMesh(MeshLoader *previewMesh) { delete m_previewMesh; @@ -290,7 +290,8 @@ public: { if (m_previewMeshs.empty()) return nullptr; - return new MeshLoader(*m_previewMeshs[0].second); + int middle = std::max((int)m_previewMeshs.size() / 2 - 1, (int)0); + return new MeshLoader(*m_previewMeshs[middle].second); } private: Q_DISABLE_COPY(Motion); @@ -424,8 +425,8 @@ signals: void poseRemoved(QUuid); void poseListChanged(); void poseNameChanged(QUuid poseId); - void poseParametersChanged(QUuid poseId); - void poseAttributesChanged(QUuid poseId); + void poseFramesChanged(QUuid poseId); + void poseTurnaroundImageIdChanged(QUuid poseId); void posePreviewChanged(QUuid poseId); void motionAdded(QUuid motionId); void motionRemoved(QUuid motionId); @@ -591,10 +592,11 @@ public slots: void toggleSmoothNormal(); void enableWeld(bool enabled); void setRigType(RigType toRigType); - void addPose(QString name, std::map> parameters); + void addPose(QString name, std::vector, std::map>>> frames, + QUuid turnaroundImageId); void removePose(QUuid poseId); - void setPoseParameters(QUuid poseId, std::map> parameters); - void setPoseAttributes(QUuid poseId, std::map attributes); + void setPoseFrames(QUuid poseId, std::vector, std::map>>> frames); + void setPoseTurnaroundImageId(QUuid poseId, QUuid imageId); void renamePose(QUuid poseId, QString name); void addMotion(QString name, std::vector clips); void removeMotion(QUuid motionId); diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index 22e62ced..4ec43de1 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -874,7 +874,7 @@ DocumentWindow::DocumentWindow() : Q_UNUSED(poseId); m_document->generatePosePreviews(); }); - connect(m_document, &Document::poseParametersChanged, this, [=](QUuid poseId) { + connect(m_document, &Document::poseFramesChanged, this, [=](QUuid poseId) { Q_UNUSED(poseId); m_document->generatePosePreviews(); }); @@ -1112,7 +1112,7 @@ void DocumentWindow::saveTo(const QString &saveAsFilename) } std::set imageIds; - for (auto &material: snapshot.materials) { + for (const auto &material: snapshot.materials) { for (auto &layer: material.second) { for (auto &mapItem: layer.second) { auto findImageIdString = mapItem.find("linkData"); diff --git a/src/materialeditwidget.cpp b/src/materialeditwidget.cpp index 80400459..37bc21e5 100644 --- a/src/materialeditwidget.cpp +++ b/src/materialeditwidget.cpp @@ -118,7 +118,7 @@ MaterialEditWidget::MaterialEditWidget(const Document *document, QWidget *parent m_unsaved = true; updateTitle(); }); - QPushButton *saveButton = new QPushButton(tr("Save")); + QPushButton *saveButton = new QPushButton(tr("Apply")); connect(saveButton, &QPushButton::clicked, this, &MaterialEditWidget::save); saveButton->setDefault(true); diff --git a/src/motioneditwidget.cpp b/src/motioneditwidget.cpp index 274c47ab..a09b8af9 100644 --- a/src/motioneditwidget.cpp +++ b/src/motioneditwidget.cpp @@ -113,7 +113,7 @@ MotionEditWidget::MotionEditWidget(const Document *document, QWidget *parent) : m_nameEdit = new QLineEdit; m_nameEdit->setFixedWidth(200); connect(m_nameEdit, &QLineEdit::textChanged, this, &MotionEditWidget::setUnsavedState); - QPushButton *saveButton = new QPushButton(tr("Save")); + QPushButton *saveButton = new QPushButton(tr("Apply")); connect(saveButton, &QPushButton::clicked, this, &MotionEditWidget::save); saveButton->setDefault(true); @@ -250,7 +250,7 @@ void MotionEditWidget::generatePreviews() m_previewsGenerator = new MotionsGenerator(m_document->rigType, rigBones, rigWeights, m_document->currentRiggedOutcome()); for (const auto &pose: m_document->poseMap) - m_previewsGenerator->addPoseToLibrary(pose.first, pose.second.parameters); + m_previewsGenerator->addPoseToLibrary(pose.first, pose.second.frames); for (const auto &motion: m_document->motionMap) m_previewsGenerator->addMotionToLibrary(motion.first, motion.second.clips); m_previewsGenerator->addMotionToLibrary(QUuid(), m_timelineWidget->clips()); diff --git a/src/motionmanagewidget.cpp b/src/motionmanagewidget.cpp index 25080bd5..63002329 100644 --- a/src/motionmanagewidget.cpp +++ b/src/motionmanagewidget.cpp @@ -22,7 +22,7 @@ MotionManageWidget::MotionManageWidget(const Document *document, QWidget *parent connect(m_motionListWidget, &MotionListWidget::modifyMotion, this, &MotionManageWidget::showMotionDialog); InfoLabel *infoLabel = new InfoLabel; - infoLabel->show(); + infoLabel->hide(); auto refreshInfoLabel = [=]() { if (m_document->currentRigSucceed()) { diff --git a/src/motionsgenerator.cpp b/src/motionsgenerator.cpp index 7d83a6cf..d0207ea9 100644 --- a/src/motionsgenerator.cpp +++ b/src/motionsgenerator.cpp @@ -26,9 +26,9 @@ MotionsGenerator::~MotionsGenerator() delete m_poser; } -void MotionsGenerator::addPoseToLibrary(const QUuid &poseId, const std::map> ¶meters) +void MotionsGenerator::addPoseToLibrary(const QUuid &poseId, const std::vector, std::map>>> &frames) { - m_poses[poseId] = parameters; + m_poses[poseId] = frames; } void MotionsGenerator::addMotionToLibrary(const QUuid &motionId, const std::vector &clips) @@ -60,6 +60,15 @@ std::vector *MotionsGenerator::findMotionClips(const QUuid &motionId return &clips; } +std::vector, std::map>>> *MotionsGenerator::findPoseFrames(const QUuid &poseId) +{ + auto findPoseResult = m_poses.find(poseId); + if (findPoseResult == m_poses.end()) + return nullptr; + std::vector, std::map>>> &frames = findPoseResult->second; + return &frames; +} + void MotionsGenerator::generatePreviewsForOutcomes(const std::vector> &outcomes, std::vector> &previews) { for (const auto &item: outcomes) { @@ -70,6 +79,18 @@ void MotionsGenerator::generatePreviewsForOutcomes(const std::vector, std::map>>> *pose = findPoseFrames(poseId); + if (nullptr == pose) + return 0; + float totalDuration = 0; + for (const auto &frame: *pose) { + totalDuration += valueOfKeyInMapOrEmpty(frame.first, "duration").toFloat(); + } + return totalDuration; +} + float MotionsGenerator::calculateMotionDuration(const QUuid &motionId, std::set &visited) { const std::vector *motionClips = findMotionClips(motionId); @@ -85,7 +106,7 @@ float MotionsGenerator::calculateMotionDuration(const QUuid &motionId, std::set< if (clip.clipType == MotionClipType::Interpolation) totalDuration += clip.duration; else if (clip.clipType == MotionClipType::Pose) - totalDuration += clip.duration; + totalDuration += calculatePoseDuration(clip.linkToId); else if (clip.clipType == MotionClipType::Motion) totalDuration += calculateMotionDuration(clip.linkToId, visited); } @@ -111,6 +132,8 @@ void MotionsGenerator::generateMotion(const QUuid &motionId, std::set &vi if (clip.clipType == MotionClipType::Motion) { std::set subVisited; clip.duration = calculateMotionDuration(clip.linkToId, subVisited); + } else if (clip.clipType == MotionClipType::Pose) { + clip.duration = calculatePoseDuration(clip.linkToId); } timePoints.push_back(totalDuration); totalDuration += clip.duration; @@ -159,13 +182,15 @@ void MotionsGenerator::generateMotion(const QUuid &motionId, std::set &vi progress += interval; continue; } else if (MotionClipType::Pose == progressClip.clipType) { - const JointNodeTree *beginJointNodeTree = findClipBeginJointNodeTree((*motionClips)[clipIndex]); - if (nullptr == beginJointNodeTree) { - qDebug() << "findClipBeginJointNodeTree failed"; - break; + const auto &frames = findPoseFrames(progressClip.linkToId); + int frame = clipLocalProgress * frames->size() / std::max((float)0.01, progressClip.duration); + if (frame >= (int)frames->size()) + frame = frames->size() - 1; + if (frame >= 0 && frame < (int)frames->size()) { + const JointNodeTree jointNodeTree = poseJointNodeTree(progressClip.linkToId, frame); + outcomes.push_back({progress - lastProgress, jointNodeTree}); + lastProgress = progress; } - outcomes.push_back({progress - lastProgress, *beginJointNodeTree}); - lastProgress = progress; progress += interval; continue; } else if (MotionClipType::Motion == progressClip.clipType) { @@ -182,25 +207,28 @@ JointNodeTree MotionsGenerator::generateInterpolation(InterpolationType interpol return JointNodeTree::slerp(first, second, calculateInterpolation(interpolationType, progress)); } -const JointNodeTree &MotionsGenerator::poseJointNodeTree(const QUuid &poseId) +const JointNodeTree &MotionsGenerator::poseJointNodeTree(const QUuid &poseId, int frame) { - auto findResult = m_poseJointNodeTreeMap.find(poseId); + auto findResult = m_poseJointNodeTreeMap.find({poseId, frame}); if (findResult != m_poseJointNodeTreeMap.end()) return findResult->second; - const auto ¶meters = m_poses[poseId]; + const auto &frames = m_poses[poseId]; m_poser->reset(); - m_poser->parameters() = parameters; + if (frame < (int)frames.size()) { + const auto ¶meters = frames[frame].second; + m_poser->parameters() = parameters; + } m_poser->commit(); - auto insertResult = m_poseJointNodeTreeMap.insert({poseId, m_poser->resultJointNodeTree()}); + auto insertResult = m_poseJointNodeTreeMap.insert({{poseId, frame}, m_poser->resultJointNodeTree()}); return insertResult.first->second; } const JointNodeTree *MotionsGenerator::findClipBeginJointNodeTree(const MotionClip &clip) { if (MotionClipType::Pose == clip.clipType) { - const JointNodeTree &jointNodeTree = poseJointNodeTree(clip.linkToId); + const JointNodeTree &jointNodeTree = poseJointNodeTree(clip.linkToId, 0); return &jointNodeTree; } else if (MotionClipType::Motion == clip.clipType) { const std::vector *motionClips = findMotionClips(clip.linkToId); @@ -215,8 +243,11 @@ const JointNodeTree *MotionsGenerator::findClipBeginJointNodeTree(const MotionCl const JointNodeTree *MotionsGenerator::findClipEndJointNodeTree(const MotionClip &clip) { if (MotionClipType::Pose == clip.clipType) { - const JointNodeTree &jointNodeTree = poseJointNodeTree(clip.linkToId); - return &jointNodeTree; + const std::vector, std::map>>> *poseFrames = findPoseFrames(clip.linkToId); + if (nullptr != poseFrames && !poseFrames->empty()) { + return &poseJointNodeTree(clip.linkToId, poseFrames->size() - 1); + } + return nullptr; } else if (MotionClipType::Motion == clip.clipType) { const std::vector *motionClips = findMotionClips(clip.linkToId); if (nullptr != motionClips && !motionClips->empty()) { diff --git a/src/motionsgenerator.h b/src/motionsgenerator.h index e7c38988..2cdf9557 100644 --- a/src/motionsgenerator.h +++ b/src/motionsgenerator.h @@ -19,7 +19,7 @@ public: const std::map *rigWeights, const Outcome &outcome); ~MotionsGenerator(); - void addPoseToLibrary(const QUuid &poseId, const std::map> ¶meters); + void addPoseToLibrary(const QUuid &poseId, const std::vector, std::map>>> &frames); void addMotionToLibrary(const QUuid &motionId, const std::vector &clips); void addRequirement(const QUuid &motionId); std::vector> takeResultPreviewMeshs(const QUuid &motionId); @@ -35,25 +35,27 @@ public slots: private: void generateMotion(const QUuid &motionId, std::set &visited, std::vector> &outcomes); - const JointNodeTree &poseJointNodeTree(const QUuid &poseId); + const JointNodeTree &poseJointNodeTree(const QUuid &poseId, int frame); JointNodeTree generateInterpolation(InterpolationType interpolationType, const JointNodeTree &first, const JointNodeTree &second, float progress); const JointNodeTree *findClipBeginJointNodeTree(const MotionClip &clip); const JointNodeTree *findClipEndJointNodeTree(const MotionClip &clip); std::vector *findMotionClips(const QUuid &motionId); + std::vector, std::map>>> *findPoseFrames(const QUuid &poseId); void generatePreviewsForOutcomes(const std::vector> &outcomes, std::vector> &previews); float calculateMotionDuration(const QUuid &motionId, std::set &visited); + float calculatePoseDuration(const QUuid &poseId); RigType m_rigType = RigType::None; std::vector m_rigBones; std::map m_rigWeights; Outcome m_outcome; - std::map>> m_poses; + std::map, std::map>>>> m_poses; std::map> m_motions; std::set m_requiredMotionIds; std::set m_generatedMotionIds; std::map>> m_resultPreviewMeshs; std::map>> m_resultJointNodeTrees; - std::map m_poseJointNodeTreeMap; + std::map, JointNodeTree> m_poseJointNodeTreeMap; Poser *m_poser = nullptr; int m_fps = 30; }; diff --git a/src/posedocument.cpp b/src/posedocument.cpp index 2bf33740..76ab9a8a 100644 --- a/src/posedocument.cpp +++ b/src/posedocument.cpp @@ -1,7 +1,14 @@ #include +#include +#include +#include +#include #include "posedocument.h" #include "rigger.h" #include "util.h" +#include "document.h" +#include "snapshot.h" +#include "snapshotxml.h" const float PoseDocument::m_nodeRadius = 0.01; const float PoseDocument::m_groundPlaneHalfThickness = 0.01 / 4; @@ -10,6 +17,12 @@ const float PoseDocument::m_outcomeScaleFactor = 0.5; bool PoseDocument::hasPastableNodesInClipboard() const { + const QClipboard *clipboard = QApplication::clipboard(); + const QMimeData *mimeData = clipboard->mimeData(); + if (mimeData->hasText()) { + if (-1 != mimeData->text().indexOf("text().indexOf(" nodeIdSet) const { - // TODO: + std::map> parameters; + toParameters(parameters, nodeIdSet); + if (parameters.empty()) + return; + Document document; + QUuid poseId = QUuid::createUuid(); + auto &pose = document.poseMap[poseId]; + pose.id = poseId; + pose.frames.push_back({std::map(), parameters}); + document.poseIdList.push_back(poseId); + + Snapshot snapshot; + std::set limitPoseIds; + document.toSnapshot(&snapshot, limitPoseIds, DocumentToSnapshotFor::Poses); + QString snapshotXml; + QXmlStreamWriter xmlStreamWriter(&snapshotXml); + saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(snapshotXml); } void PoseDocument::saveHistoryItem() @@ -72,6 +105,21 @@ void PoseDocument::redo() void PoseDocument::paste() { + const QClipboard *clipboard = QApplication::clipboard(); + const QMimeData *mimeData = clipboard->mimeData(); + if (mimeData->hasText()) { + QXmlStreamReader xmlStreamReader(mimeData->text()); + Snapshot snapshot; + loadSkeletonFromXmlStream(&snapshot, xmlStreamReader); + if (snapshot.poses.empty()) + return; + const auto &firstPose = *snapshot.poses.begin(); + if (firstPose.second.empty()) + return; + const auto &firstFrame = *firstPose.second.begin(); + fromParameters(&m_riggerBones, firstFrame.second); + saveHistoryItem(); + } } void PoseDocument::updateTurnaround(const QImage &image) @@ -93,6 +141,12 @@ void PoseDocument::reset() emit parametersChanged(); } +void PoseDocument::clearHistories() +{ + m_undoItems.clear(); + m_redoItems.clear(); +} + void PoseDocument::fromParameters(const std::vector *rigBones, const std::map> ¶meters) { @@ -390,7 +444,7 @@ float PoseDocument::findGroundY() const return maxY; } -void PoseDocument::toParameters(std::map> ¶meters) const +void PoseDocument::toParameters(std::map> ¶meters, const std::set &limitNodeIds) const { float translateY = 0; auto findGroundEdge = edgeMap.find(m_groundEdgeId); @@ -400,6 +454,8 @@ void PoseDocument::toParameters(std::map> &p auto findFirstNode = nodeMap.find(nodeIds[0]); auto findSecondNode = nodeMap.find(nodeIds[1]); if (findFirstNode != nodeMap.end() && findSecondNode != nodeMap.end()) { + if (limitNodeIds.empty() || limitNodeIds.find(findFirstNode->first) != limitNodeIds.end() || + limitNodeIds.find(findSecondNode->first) != limitNodeIds.end()) translateY = (findFirstNode->second.y + findSecondNode->second.y) / 2 - (findGroundY() + m_groundPlaneHalfThickness); } @@ -417,13 +473,16 @@ void PoseDocument::toParameters(std::map> &p auto findSecondNode = nodeMap.find(boneNodeIdPair.second); if (findSecondNode == nodeMap.end()) continue; - auto &boneParameter = parameters[item.first]; - boneParameter["fromX"] = QString::number(toOutcomeX(findFirstNode->second.x)); - boneParameter["fromY"] = QString::number(toOutcomeY(findFirstNode->second.y)); - boneParameter["fromZ"] = QString::number(toOutcomeZ(findFirstNode->second.z)); - boneParameter["toX"] = QString::number(toOutcomeX(findSecondNode->second.x)); - boneParameter["toY"] = QString::number(toOutcomeY(findSecondNode->second.y)); - boneParameter["toZ"] = QString::number(toOutcomeZ(findSecondNode->second.z)); + if (limitNodeIds.empty() || limitNodeIds.find(boneNodeIdPair.first) != limitNodeIds.end() || + limitNodeIds.find(boneNodeIdPair.second) != limitNodeIds.end()) { + auto &boneParameter = parameters[item.first]; + boneParameter["fromX"] = QString::number(toOutcomeX(findFirstNode->second.x)); + boneParameter["fromY"] = QString::number(toOutcomeY(findFirstNode->second.y)); + boneParameter["fromZ"] = QString::number(toOutcomeZ(findFirstNode->second.z)); + boneParameter["toX"] = QString::number(toOutcomeX(findSecondNode->second.x)); + boneParameter["toY"] = QString::number(toOutcomeY(findSecondNode->second.y)); + boneParameter["toZ"] = QString::number(toOutcomeZ(findSecondNode->second.z)); + } } } diff --git a/src/posedocument.h b/src/posedocument.h index 08f3dce7..cb2e03bd 100644 --- a/src/posedocument.h +++ b/src/posedocument.h @@ -35,12 +35,13 @@ public: void updateRigBones(const std::vector *rigBones, const QVector3D &rootTranslation=QVector3D(0, 0, 0)); void reset(); - void toParameters(std::map> ¶meters) const; + void toParameters(std::map> ¶meters, const std::set &limitNodeIds=std::set()) const; void fromParameters(const std::vector *rigBones, const std::map> ¶meters); public slots: void saveHistoryItem(); + void clearHistories(); void undo() override; void redo() override; void paste() override; diff --git a/src/poseeditwidget.cpp b/src/poseeditwidget.cpp index 908703dd..73eb865c 100644 --- a/src/poseeditwidget.cpp +++ b/src/poseeditwidget.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "theme.h" #include "poseeditwidget.h" #include "floatnumberwidget.h" @@ -17,6 +18,8 @@ #include "shortcuts.h" #include "imageforever.h" +const float PoseEditWidget::m_frameDuration = 0.042; //(1.0 / 24) + PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : QDialog(parent), m_document(document), @@ -75,8 +78,9 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : connect(m_poseDocument, &PoseDocument::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged); connect(m_poseDocument, &PoseDocument::parametersChanged, this, [&]() { - m_parameters.clear(); - m_poseDocument->toParameters(m_parameters); + m_currentParameters.clear(); + m_poseDocument->toParameters(m_currentParameters); + syncFrameFromCurrent(); emit parametersAdjusted(); }); @@ -86,10 +90,9 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : m_nameEdit = new QLineEdit; m_nameEdit->setFixedWidth(200); connect(m_nameEdit, &QLineEdit::textChanged, this, [=]() { - m_unsaved = true; - updateTitle(); + setUnsaveState(); }); - QPushButton *saveButton = new QPushButton(tr("Save")); + QPushButton *saveButton = new QPushButton(tr("Apply")); connect(saveButton, &QPushButton::clicked, this, &PoseEditWidget::save); saveButton->setDefault(true); @@ -98,14 +101,30 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : connect(m_poseDocument, &PoseDocument::turnaroundChanged, graphicsWidget, &SkeletonGraphicsWidget::turnaroundChanged); + m_framesSettingButton = new QPushButton(); + connect(m_framesSettingButton, &QPushButton::clicked, this, [=]() { + showFramesSettingPopup(mapFromGlobal(QCursor::pos())); + }); + + m_currentFrameSlider = new QSlider(Qt::Horizontal); + m_currentFrameSlider->setRange(0, m_frames.size() - 1); + m_currentFrameSlider->setValue(m_currentFrame); + m_currentFrameSlider->hide(); + connect(m_currentFrameSlider, static_cast(&QSlider::valueChanged), this, [=](int value) { + setCurrentFrame(value); + }); + connect(m_document, &Document::resultRigChanged, this, &PoseEditWidget::updatePoseDocument); QHBoxLayout *baseInfoLayout = new QHBoxLayout; baseInfoLayout->addWidget(new QLabel(tr("Name"))); baseInfoLayout->addWidget(m_nameEdit); baseInfoLayout->addWidget(changeReferenceSheet); + baseInfoLayout->addWidget(m_framesSettingButton); + baseInfoLayout->addWidget(m_currentFrameSlider); baseInfoLayout->addStretch(); baseInfoLayout->addWidget(saveButton); + baseInfoLayout->setStretch(4, 1); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(paramtersLayout); @@ -114,22 +133,99 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : setLayout(mainLayout); - connect(m_document, &Document::resultRigChanged, this, &PoseEditWidget::updatePoseDocument); connect(this, &PoseEditWidget::parametersAdjusted, this, &PoseEditWidget::updatePreview); connect(this, &PoseEditWidget::parametersAdjusted, [=]() { - m_unsaved = true; - updateTitle(); + setUnsaveState(); }); connect(this, &PoseEditWidget::addPose, m_document, &Document::addPose); connect(this, &PoseEditWidget::renamePose, m_document, &Document::renamePose); - connect(this, &PoseEditWidget::setPoseParameters, m_document, &Document::setPoseParameters); - connect(this, &PoseEditWidget::setPoseAttributes, m_document, &Document::setPoseAttributes); + connect(this, &PoseEditWidget::setPoseFrames, m_document, &Document::setPoseFrames); + connect(this, &PoseEditWidget::setPoseTurnaroundImageId, m_document, &Document::setPoseTurnaroundImageId); updatePoseDocument(); updateTitle(); + updateFramesSettingButton(); m_poseDocument->saveHistoryItem(); } +void PoseEditWidget::showFramesSettingPopup(const QPoint &pos) +{ + QMenu popupMenu; + + QWidget *popup = new QWidget; + + QSpinBox *framesEdit = new QSpinBox(); + framesEdit->setMaximum(60); + framesEdit->setMinimum(1); + framesEdit->setSingleStep(1); + framesEdit->setValue(m_frames.size()); + + connect(framesEdit, static_cast(&QSpinBox::valueChanged), this, [=](int value) { + setFrameCount(value); + }); + + QFormLayout *formLayout = new QFormLayout; + formLayout->addRow(tr("Frames:"), framesEdit); + + popup->setLayout(formLayout); + + QWidgetAction *action = new QWidgetAction(this); + action->setDefaultWidget(popup); + + popupMenu.addAction(action); + + popupMenu.exec(mapToGlobal(pos)); +} + +void PoseEditWidget::updateFramesSettingButton() +{ + m_currentFrameSlider->setRange(0, m_frames.size() - 1); + m_currentFrameSlider->setVisible(m_frames.size() > 1); + m_framesSettingButton->setText(tr("Frame: %1 / %2").arg(QString::number(m_currentFrame + 1).rightJustified(2, ' ')).arg(QString::number(m_frames.size()).leftJustified(2, ' '))); +} + +void PoseEditWidget::ensureEnoughFrames() +{ + if (m_currentFrame >= (int)m_frames.size()) { + m_frames.resize(m_currentFrame + 1); + setUnsaveState(); + updateFramesSettingButton(); + } +} + +void PoseEditWidget::syncFrameFromCurrent() +{ + ensureEnoughFrames(); + m_frames[m_currentFrame] = {m_currentAttributes, m_currentParameters}; + m_frames[m_currentFrame].first["duration"] = QString::number(m_frameDuration); +} + +void PoseEditWidget::setFrameCount(int count) +{ + if (count == (int)m_frames.size()) + return; + + setUnsaveState(); + count = std::max(count, 1); + m_frames.resize(count); + updateFramesSettingButton(); + if (m_currentFrame >= count) { + setCurrentFrame(count - 1); + } +} + +void PoseEditWidget::setCurrentFrame(int frame) +{ + if (m_currentFrame == frame) + return; + m_currentFrame = frame; + ensureEnoughFrames(); + updateFramesSettingButton(); + m_currentAttributes = m_frames[m_currentFrame].first; + m_currentParameters = m_frames[m_currentFrame].second; + updatePoseDocument(); +} + void PoseEditWidget::changeTurnaround() { QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(), @@ -139,26 +235,19 @@ void PoseEditWidget::changeTurnaround() QImage image; if (!image.load(fileName)) return; - m_imageId = ImageForever::add(&image); - m_attributes["canvasImageId"] = m_imageId.toString(); + auto newImageId = ImageForever::add(&image); + if (m_imageId == newImageId) + return; + setUnsaveState(); + m_imageId = newImageId; m_poseDocument->updateTurnaround(image); } -QUuid PoseEditWidget::findImageIdFromAttributes(const std::map &attributes) -{ - auto findImageIdResult = attributes.find("canvasImageId"); - if (findImageIdResult == attributes.end()) - return QUuid(); - return QUuid(findImageIdResult->second); -} - void PoseEditWidget::updatePoseDocument() { - m_poseDocument->fromParameters(m_document->resultRigBones(), m_parameters); - QUuid imageId = findImageIdFromAttributes(m_attributes); - auto image = ImageForever::get(imageId); - if (nullptr != image) - m_poseDocument->updateTurnaround(*image); + m_poseDocument->fromParameters(m_document->resultRigBones(), m_currentParameters); + m_poseDocument->clearHistories(); + m_poseDocument->saveHistoryItem(); updatePreview(); } @@ -222,10 +311,7 @@ void PoseEditWidget::updatePreview() if (nullptr == poser) return; - m_parameters.clear(); - m_poseDocument->toParameters(m_parameters); - - poser->parameters() = m_parameters; + poser->parameters() = m_currentParameters; poser->commit(); m_posePreviewManager->postUpdate(*poser, m_document->currentRiggedOutcome(), *rigWeights); delete poser; @@ -260,19 +346,28 @@ void PoseEditWidget::setEditPoseName(QString name) updateTitle(); } -void PoseEditWidget::setEditParameters(std::map> parameters) +void PoseEditWidget::setEditPoseFrames(std::vector, std::map>>> frames) { - m_parameters = parameters; + m_frames = frames; + if (!m_frames.empty()) { + m_currentFrame = 0; + const auto &frame = m_frames[m_currentFrame]; + m_currentAttributes = frame.first; + m_currentParameters = frame.second; + } updatePoseDocument(); updatePreview(); + updateFramesSettingButton(); m_poseDocument->saveHistoryItem(); } -void PoseEditWidget::setEditAttributes(std::map attributes) +void PoseEditWidget::setEditPoseTurnaroundImageId(QUuid imageId) { - m_attributes = attributes; - updatePoseDocument(); - updatePreview(); + m_imageId = imageId; + const auto &image = ImageForever::get(m_imageId); + if (nullptr == image) + return; + m_poseDocument->updateTurnaround(*image); } void PoseEditWidget::clearUnsaveState() @@ -281,14 +376,20 @@ void PoseEditWidget::clearUnsaveState() updateTitle(); } +void PoseEditWidget::setUnsaveState() +{ + m_unsaved = true; + updateTitle(); +} + void PoseEditWidget::save() { if (m_poseId.isNull()) { - emit addPose(m_nameEdit->text(), m_parameters); + emit addPose(m_nameEdit->text(), m_frames, m_imageId); } else if (m_unsaved) { emit renamePose(m_poseId, m_nameEdit->text()); - emit setPoseParameters(m_poseId, m_parameters); - emit setPoseAttributes(m_poseId, m_attributes); + emit setPoseFrames(m_poseId, m_frames); + emit setPoseTurnaroundImageId(m_poseId, m_imageId); } m_unsaved = false; close(); diff --git a/src/poseeditwidget.h b/src/poseeditwidget.h index a1ec1d82..f4940454 100644 --- a/src/poseeditwidget.h +++ b/src/poseeditwidget.h @@ -4,56 +4,71 @@ #include #include #include +#include #include "posepreviewmanager.h" #include "document.h" #include "modelwidget.h" #include "rigger.h" #include "skeletongraphicswidget.h" #include "posedocument.h" +#include "floatnumberwidget.h" class PoseEditWidget : public QDialog { Q_OBJECT signals: - void addPose(QString name, std::map> parameters); + void addPose(QString name, std::vector, std::map>>> frames, QUuid turnaroundImageId); void removePose(QUuid poseId); - void setPoseParameters(QUuid poseId, std::map> parameters); - void setPoseAttributes(QUuid poseId, std::map attributes); + void setPoseFrames(QUuid poseId, std::vector, std::map>>> frames); + void setPoseTurnaroundImageId(QUuid poseId, QUuid imageId); void renamePose(QUuid poseId, QString name); void parametersAdjusted(); public: PoseEditWidget(const Document *document, QWidget *parent=nullptr); ~PoseEditWidget(); + + static const float m_frameDuration; public slots: void updatePoseDocument(); void updatePreview(); + void syncFrameFromCurrent(); void setEditPoseId(QUuid poseId); void setEditPoseName(QString name); - void setEditParameters(std::map> parameters); - void setEditAttributes(std::map attributes); + void setEditPoseFrames(std::vector, std::map>>> frames); + void setEditPoseTurnaroundImageId(QUuid imageId); + void setCurrentFrame(int frame); + void setFrameCount(int count); void updateTitle(); void save(); void clearUnsaveState(); + void setUnsaveState(); void changeTurnaround(); +private slots: + void updateFramesSettingButton(); + void showFramesSettingPopup(const QPoint &pos); protected: QSize sizeHint() const override; void closeEvent(QCloseEvent *event) override; void reject() override; private: + void ensureEnoughFrames(); const Document *m_document = nullptr; PosePreviewManager *m_posePreviewManager = nullptr; ModelWidget *m_previewWidget = nullptr; bool m_isPreviewDirty = false; bool m_closed = false; - std::map> m_parameters; - std::map m_attributes; + std::vector, std::map>>> m_frames; + std::map m_currentAttributes; + std::map> m_currentParameters; + int m_currentFrame = 0; QUuid m_poseId; bool m_unsaved = false; QUuid m_imageId; QLineEdit *m_nameEdit = nullptr; PoseDocument *m_poseDocument = nullptr; SkeletonGraphicsWidget *m_poseGraphicsWidget = nullptr; - QUuid findImageIdFromAttributes(const std::map &attributes); + QPushButton *m_framesSettingButton = nullptr; + QSlider *m_currentFrameSlider = nullptr; }; #endif diff --git a/src/posemanagewidget.cpp b/src/posemanagewidget.cpp index 24b329a0..40bb4d89 100644 --- a/src/posemanagewidget.cpp +++ b/src/posemanagewidget.cpp @@ -22,7 +22,7 @@ PoseManageWidget::PoseManageWidget(const Document *document, QWidget *parent) : connect(m_poseListWidget, &PoseListWidget::modifyPose, this, &PoseManageWidget::showPoseDialog); InfoLabel *infoLabel = new InfoLabel; - infoLabel->show(); + infoLabel->hide(); auto refreshInfoLabel = [=]() { if (m_document->currentRigSucceed()) { @@ -77,8 +77,8 @@ void PoseManageWidget::showPoseDialog(QUuid poseId) if (nullptr != pose) { poseEditWidget->setEditPoseId(poseId); poseEditWidget->setEditPoseName(pose->name); - poseEditWidget->setEditParameters(pose->parameters); - poseEditWidget->setEditAttributes(pose->attributes); + poseEditWidget->setEditPoseFrames(pose->frames); + poseEditWidget->setEditPoseTurnaroundImageId(pose->turnaroundImageId); poseEditWidget->clearUnsaveState(); } } diff --git a/src/posepreviewsgenerator.cpp b/src/posepreviewsgenerator.cpp index 22a5b165..26e6a469 100644 --- a/src/posepreviewsgenerator.cpp +++ b/src/posepreviewsgenerator.cpp @@ -23,20 +23,20 @@ PosePreviewsGenerator::~PosePreviewsGenerator() delete m_outcome; } -void PosePreviewsGenerator::addPose(QUuid poseId, const std::map> &pose) +void PosePreviewsGenerator::addPose(std::pair idAndFrame, const std::map> &pose) { - m_poses.push_back(std::make_pair(poseId, pose)); + m_poses.push_back(std::make_pair(idAndFrame, pose)); } -const std::set &PosePreviewsGenerator::generatedPreviewPoseIds() +const std::set> &PosePreviewsGenerator::generatedPreviewPoseIdAndFrames() { - return m_generatedPoseIds; + return m_generatedPoseIdAndFrames; } -MeshLoader *PosePreviewsGenerator::takePreview(QUuid poseId) +MeshLoader *PosePreviewsGenerator::takePreview(std::pair idAndFrame) { - MeshLoader *resultMesh = m_previews[poseId]; - m_previews[poseId] = nullptr; + MeshLoader *resultMesh = m_previews[idAndFrame]; + m_previews[idAndFrame] = nullptr; return resultMesh; } @@ -57,7 +57,7 @@ void PosePreviewsGenerator::process() poser->reset(); - m_generatedPoseIds.insert(pose.first); + m_generatedPoseIdAndFrames.insert(pose.first); } delete poser; diff --git a/src/posepreviewsgenerator.h b/src/posepreviewsgenerator.h index b011f304..d209557d 100644 --- a/src/posepreviewsgenerator.h +++ b/src/posepreviewsgenerator.h @@ -18,9 +18,9 @@ public: const std::map *rigWeights, const Outcome &outcome); ~PosePreviewsGenerator(); - void addPose(QUuid poseId, const std::map> &pose); - const std::set &generatedPreviewPoseIds(); - MeshLoader *takePreview(QUuid poseId); + void addPose(std::pair idAndFrame, const std::map> &pose); + const std::set> &generatedPreviewPoseIdAndFrames(); + MeshLoader *takePreview(std::pair idAndFrame); signals: void finished(); public slots: @@ -30,9 +30,9 @@ private: std::vector m_rigBones; std::map m_rigWeights; Outcome *m_outcome = nullptr; - std::vector>>> m_poses; - std::map m_previews; - std::set m_generatedPoseIds; + std::vector, std::map>>> m_poses; + std::map, MeshLoader *> m_previews; + std::set> m_generatedPoseIdAndFrames; }; #endif diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index 5eb9ec27..37cc1055 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -163,13 +163,13 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos) } QAction copyAction(tr("Copy"), this); - if (!m_nodePositionModifyOnly && hasSelection()) { + if (hasNodeSelection()) { connect(©Action, &QAction::triggered, this, &SkeletonGraphicsWidget::copy); contextMenu.addAction(©Action); } QAction pasteAction(tr("Paste"), this); - if (!m_nodePositionModifyOnly && m_document->hasPastableNodesInClipboard()) { + if (m_document->hasPastableNodesInClipboard()) { connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste); contextMenu.addAction(&pasteAction); } diff --git a/src/snapshot.h b/src/snapshot.h index df05d152..c9aca986 100644 --- a/src/snapshot.h +++ b/src/snapshot.h @@ -18,7 +18,7 @@ public: std::map> parts; std::map> components; std::map rootComponent; - std::vector, std::map>>> poses; // std::pair + std::vector, std::vector, std::map>>>>> poses; // std::pair frame: std::pair std::vector, std::vector>>> motions; // std::pair std::vector, std::vector, std::vector>>>>> materials; // std::pair layer: std::pair @@ -72,11 +72,17 @@ public: addQStringToBuffer(subItem.second); } for (const auto &subItem: item.second) { - addQStringToBuffer(subItem.first); - for (const auto &subSubItem: subItem.second) { + for (const auto &subSubItem: subItem.first) { addQStringToBuffer(subSubItem.first); addQStringToBuffer(subSubItem.second); } + for (const auto &subSubItem: subItem.second) { + addQStringToBuffer(subSubItem.first); + for (const auto &subSubSubItem: subSubItem.second) { + addQStringToBuffer(subSubSubItem.first); + addQStringToBuffer(subSubSubItem.second); + } + } } } for (const auto &item: motions) { diff --git a/src/snapshotxml.cpp b/src/snapshotxml.cpp index e227743b..22f82978 100644 --- a/src/snapshotxml.cpp +++ b/src/snapshotxml.cpp @@ -127,25 +127,37 @@ void saveSkeletonToXmlStream(Snapshot *snapshot, QXmlStreamWriter *writer) writer->writeEndElement(); writer->writeStartElement("poses"); - std::vector, std::map>>>::iterator poseIterator; + //std::vector, std::map>>>::iterator poseIterator; + std::vector, std::vector, std::map>>>>>::iterator poseIterator; for (poseIterator = snapshot->poses.begin(); poseIterator != snapshot->poses.end(); poseIterator++) { std::map::iterator poseAttributeIterator; writer->writeStartElement("pose"); for (poseAttributeIterator = poseIterator->first.begin(); poseAttributeIterator != poseIterator->first.end(); poseAttributeIterator++) { writer->writeAttribute(poseAttributeIterator->first, poseAttributeIterator->second); } - writer->writeStartElement("parameters"); - std::map>::iterator itemsIterator; - for (itemsIterator = poseIterator->second.begin(); itemsIterator != poseIterator->second.end(); itemsIterator++) { - std::map::iterator parametersIterator; - writer->writeStartElement("parameter"); - writer->writeAttribute("for", itemsIterator->first); - for (parametersIterator = itemsIterator->second.begin(); parametersIterator != itemsIterator->second.end(); - parametersIterator++) { - writer->writeAttribute(parametersIterator->first, parametersIterator->second); + writer->writeStartElement("frames"); + std::vector, std::map>>>::iterator frameIterator; + for (frameIterator = poseIterator->second.begin(); frameIterator != poseIterator->second.end(); frameIterator++) { + std::map::iterator frameAttributeIterator; + writer->writeStartElement("frame"); + for (frameAttributeIterator = frameIterator->first.begin(); frameAttributeIterator != frameIterator->first.end(); frameAttributeIterator++) { + writer->writeAttribute(frameAttributeIterator->first, frameAttributeIterator->second); + } + writer->writeStartElement("parameters"); + std::map>::iterator itemsIterator; + for (itemsIterator = frameIterator->second.begin(); itemsIterator != frameIterator->second.end(); itemsIterator++) { + std::map::iterator parametersIterator; + writer->writeStartElement("parameter"); + writer->writeAttribute("for", itemsIterator->first); + for (parametersIterator = itemsIterator->second.begin(); parametersIterator != itemsIterator->second.end(); + parametersIterator++) { + writer->writeAttribute(parametersIterator->first, parametersIterator->second); + } + writer->writeEndElement(); + } + writer->writeEndElement(); + writer->writeEndElement(); } - writer->writeEndElement(); - } writer->writeEndElement(); writer->writeEndElement(); } @@ -188,7 +200,8 @@ void loadSkeletonFromXmlStream(Snapshot *snapshot, QXmlStreamReader &reader) std::vector elementNameStack; std::pair, std::vector>> currentMaterialLayer; std::pair, std::vector, std::vector>>>> currentMaterial; - std::pair, std::map>> currentPose; + std::pair, std::vector, std::map>>>> currentPose; + std::pair, std::map>> currentPoseFrame; std::pair, std::vector>> currentMotion; while (!reader.atEnd()) { reader.readNext(); @@ -293,15 +306,18 @@ void loadSkeletonFromXmlStream(Snapshot *snapshot, QXmlStreamReader &reader) foreach(const QXmlStreamAttribute &attr, reader.attributes()) { currentPose.first[attr.name().toString()] = attr.value().toString(); } - } else if (fullName == "canvas.poses.pose.parameter" || - fullName == "canvas.poses.pose.parameters.parameter") { + } else if (fullName == "canvas.poses.pose.frames.frame") { + foreach(const QXmlStreamAttribute &attr, reader.attributes()) { + currentPoseFrame.first[attr.name().toString()] = attr.value().toString(); + } + } else if (fullName == "canvas.poses.pose.frames.frame.parameters.parameter") { QString forWhat = reader.attributes().value("for").toString(); if (forWhat.isEmpty()) continue; foreach(const QXmlStreamAttribute &attr, reader.attributes()) { if ("for" == attr.name().toString()) continue; - currentPose.second[forWhat][attr.name().toString()] = attr.value().toString(); + currentPoseFrame.second[forWhat][attr.name().toString()] = attr.value().toString(); } } else if (fullName == "canvas.motions.motion") { QString motionId = reader.attributes().value("id").toString(); @@ -325,6 +341,8 @@ void loadSkeletonFromXmlStream(Snapshot *snapshot, QXmlStreamReader &reader) currentMaterial.second.push_back(currentMaterialLayer); } else if (fullName == "canvas.materials.material") { snapshot->materials.push_back(currentMaterial); + } else if (fullName == "canvas.poses.pose.frames.frame") { + currentPose.second.push_back(currentPoseFrame); } else if (fullName == "canvas.poses.pose") { snapshot->poses.push_back(currentPose); } else if (fullName == "canvas.motions.motion") {