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") {