From 70f075a36ef42e8d451b1f8ee8d035d1b87fa176 Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Mon, 5 Nov 2018 23:47:21 +0800 Subject: [PATCH] Rewrite pose editor This commit include lots of changes for pose editor. Before, we use buttons to represent the transform controller for bones; Now, we use nodes and edges to represent the bones, just like the nodes and edges for representing the guide spheres for mesh generating. There are advantages by doing this. Firstly, The edit UI for both mesh and pose are unified, secondly, it is possible to set a reference sheet for pose editing now, this is very important. This new pose editor is inspired by the Eadweard Muybridge's work. --- ACKNOWLEDGEMENTS.html | 7 +- dust3d.pro | 3 + src/document.cpp | 95 +-- src/document.h | 11 +- src/documentwindow.cp | 1341 -------------------------------- src/documentwindow.cpp | 213 ++--- src/documentwindow.h | 8 +- src/genericposer.cpp | 45 +- src/genericrigger.cpp | 8 +- src/main.cpp | 2 +- src/motionmanagewidget.cpp | 13 +- src/posedocument.cpp | 401 ++++++++++ src/posedocument.h | 66 ++ src/poseeditwidget.cpp | 312 +++----- src/poseeditwidget.h | 15 +- src/posemanagewidget.cpp | 14 +- src/rigger.cpp | 1 + src/rigger.h | 1 + src/shortcuts.cpp | 4 +- src/shortcuts.h | 2 +- src/skeletondocument.cpp | 71 ++ src/skeletondocument.h | 14 +- src/skeletongraphicswidget.cpp | 46 +- src/skeletongraphicswidget.h | 2 + src/tetrapodposer.cpp | 258 ++++-- src/tetrapodposer.h | 8 +- src/tetrapodrigger.cpp | 14 +- 27 files changed, 1085 insertions(+), 1890 deletions(-) delete mode 100644 src/documentwindow.cp create mode 100644 src/posedocument.cpp create mode 100644 src/posedocument.h diff --git a/ACKNOWLEDGEMENTS.html b/ACKNOWLEDGEMENTS.html index 947272f6..0ed40bfb 100644 --- a/ACKNOWLEDGEMENTS.html +++ b/ACKNOWLEDGEMENTS.html @@ -1132,4 +1132,9 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode

Birdy

     http://bediyap.com/programming/convert-quaternion-to-euler-rotations/
-
\ No newline at end of file + + +

Eadweard Muybridge

+
+    https://en.wikipedia.org/wiki/Eadweard_Muybridge
+
diff --git a/dust3d.pro b/dust3d.pro index 9642b5c2..54577290 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -294,6 +294,9 @@ HEADERS += src/poserconstruct.h SOURCES += src/skeletondocument.cpp HEADERS += src/skeletondocument.h +SOURCES += src/posedocument.cpp +HEADERS += src/posedocument.h + SOURCES += src/main.cpp HEADERS += src/version.h diff --git a/src/document.cpp b/src/document.cpp index 10371f57..ca4f2292 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -416,6 +416,20 @@ void Document::setPoseParameters(QUuid poseId, std::map attributes) +{ + auto findPoseResult = poseMap.find(poseId); + if (findPoseResult == poseMap.end()) { + qDebug() << "Find pose failed:" << poseId; + return; + } + findPoseResult->second.attributes = attributes; + findPoseResult->second.dirty = true; + emit posesChanged(); + emit poseAttributesChanged(poseId); + emit optionsChanged(); +} + void Document::renamePose(QUuid poseId, QString name) { auto findPoseResult = poseMap.find(poseId); @@ -431,26 +445,6 @@ void Document::renamePose(QUuid poseId, QString name) emit optionsChanged(); } -const SkeletonEdge *Document::findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const -{ - const SkeletonNode *firstNode = nullptr; - firstNode = findNode(firstNodeId); - if (nullptr == firstNode) { - qDebug() << "Find node failed:" << firstNodeId; - return nullptr; - } - for (auto edgeIdIt = firstNode->edgeIds.begin(); edgeIdIt != firstNode->edgeIds.end(); edgeIdIt++) { - auto edgeIt = edgeMap.find(*edgeIdIt); - if (edgeIt == edgeMap.end()) { - qDebug() << "Find edge failed:" << *edgeIdIt; - continue; - } - if (std::find(edgeIt->second.nodeIds.begin(), edgeIt->second.nodeIds.end(), secondNodeId) != edgeIt->second.nodeIds.end()) - return &edgeIt->second; - } - return nullptr; -} - bool Document::originSettled() const { return !qFuzzyIsNull(originX) && !qFuzzyIsNull(originY) && !qFuzzyIsNull(originZ); @@ -537,30 +531,6 @@ void Document::addEdge(QUuid fromNodeId, QUuid toNodeId) emit skeletonChanged(); } -const SkeletonNode *Document::findNode(QUuid nodeId) const -{ - auto it = nodeMap.find(nodeId); - if (it == nodeMap.end()) - return nullptr; - return &it->second; -} - -const SkeletonEdge *Document::findEdge(QUuid edgeId) const -{ - auto it = edgeMap.find(edgeId); - if (it == edgeMap.end()) - return nullptr; - return &it->second; -} - -const SkeletonPart *Document::findPart(QUuid partId) const -{ - auto it = partMap.find(partId); - if (it == partMap.end()) - return nullptr; - return &it->second; -} - const Component *Document::findComponent(QUuid componentId) const { if (componentId.isNull()) @@ -1001,7 +971,7 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &limitNodeId continue; } auto &poseIt = *findPoseResult; - std::map pose; + std::map pose = poseIt.second.attributes; pose["id"] = poseIt.second.id.toString(); if (!poseIt.second.name.isEmpty()) pose["name"] = poseIt.second.name; @@ -1251,6 +1221,13 @@ 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; oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(poseAttributes, "id"))] = newPoseId; poseIdList.push_back(newPoseId); @@ -2394,32 +2371,6 @@ void Document::checkExportReadyState() emit exportReady(); } -void Document::findAllNeighbors(QUuid nodeId, std::set &neighbors) const -{ - const auto &node = findNode(nodeId); - if (nullptr == node) { - qDebug() << "findNode:" << nodeId << "failed"; - return; - } - for (const auto &edgeId: node->edgeIds) { - const auto &edge = findEdge(edgeId); - if (nullptr == edge) { - qDebug() << "findEdge:" << edgeId << "failed"; - continue; - } - const auto &neighborNodeId = edge->neighborOf(nodeId); - if (neighborNodeId.isNull()) { - qDebug() << "neighborOf:" << nodeId << "is null from edge:" << edgeId; - continue; - } - if (neighbors.find(neighborNodeId) != neighbors.end()) { - continue; - } - neighbors.insert(neighborNodeId); - findAllNeighbors(neighborNodeId, neighbors); - } -} - void Document::setSharedContextWidget(QOpenGLWidget *sharedContextWidget) { m_sharedContextWidget = sharedContextWidget; @@ -2945,4 +2896,4 @@ void Document::copyNodes(std::set nodeIdSet) const saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter); QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(snapshotXml); -} \ No newline at end of file +} diff --git a/src/document.h b/src/document.h index f0614746..52f3f33f 100644 --- a/src/document.h +++ b/src/document.h @@ -195,6 +195,7 @@ public: QUuid id; QString name; bool dirty = true; + std::map attributes; std::map> parameters; void updatePreviewMesh(MeshLoader *previewMesh) { @@ -424,6 +425,7 @@ signals: void poseListChanged(); void poseNameChanged(QUuid poseId); void poseParametersChanged(QUuid poseId); + void poseAttributesChanged(QUuid poseId); void posePreviewChanged(QUuid poseId); void motionAdded(QUuid motionId); void motionRemoved(QUuid motionId); @@ -453,9 +455,6 @@ public: // need initialize public: Document(); ~Document(); - std::map partMap; - std::map nodeMap; - std::map edgeMap; std::map componentMap; std::map materialMap; std::vector materialIdList; @@ -465,17 +464,12 @@ public: std::vector motionIdList; Component rootComponent; QImage preview; - const SkeletonNode *findNode(QUuid nodeId) const override; - const SkeletonEdge *findEdge(QUuid edgeId) const override; - const SkeletonPart *findPart(QUuid partId) const override; - const SkeletonEdge *findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const override; bool undoable() const override; bool redoable() const override; bool hasPastableNodesInClipboard() const override; bool originSettled() const override; bool isNodeEditable(QUuid nodeId) const override; bool isEdgeEditable(QUuid edgeId) const override; - void findAllNeighbors(QUuid nodeId, std::set &neighbors) const override; void copyNodes(std::set nodeIdSet) const override; void toSnapshot(Snapshot *snapshot, const std::set &limitNodeIds=std::set(), DocumentToSnapshotFor forWhat=DocumentToSnapshotFor::Document, @@ -601,6 +595,7 @@ public slots: void addPose(QString name, std::map> parameters); void removePose(QUuid poseId); void setPoseParameters(QUuid poseId, std::map> parameters); + void setPoseAttributes(QUuid poseId, std::map attributes); void renamePose(QUuid poseId, QString name); void addMotion(QString name, std::vector clips); void removeMotion(QUuid motionId); diff --git a/src/documentwindow.cp b/src/documentwindow.cp deleted file mode 100644 index b408adeb..00000000 --- a/src/documentwindow.cp +++ /dev/null @@ -1,1341 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "documentwindow.h" -#include "skeletongraphicswidget.h" -#include "theme.h" -#include "ds3file.h" -#include "snapshot.h" -#include "snapshotxml.h" -#include "logbrowser.h" -#include "util.h" -#include "aboutwidget.h" -#include "version.h" -#include "gltffile.h" -#include "graphicscontainerwidget.h" -#include "parttreewidget.h" -#include "rigwidget.h" -#include "markiconcreator.h" -#include "motionmanagewidget.h" -#include "materialmanagewidget.h" -#include "imageforever.h" -#include "spinnableawesomebutton.h" -#include "fbxfile.h" -#include "shortcuts.h" - -int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16; -int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16; -int SkeletonDocumentWindow::m_modelRenderWidgetInitialSize = 128; -int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialX = SkeletonDocumentWindow::m_modelRenderWidgetInitialX + SkeletonDocumentWindow::m_modelRenderWidgetInitialSize + 16; -int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialY = SkeletonDocumentWindow::m_modelRenderWidgetInitialY; -int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialSize = SkeletonDocumentWindow::m_modelRenderWidgetInitialSize; - -LogBrowser *g_logBrowser = nullptr; -std::set g_documentWindows; -QTextBrowser *g_acknowlegementsWidget = nullptr; -AboutWidget *g_aboutWidget = nullptr; -QTextBrowser *g_contributorsWidget = nullptr; - -void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) -{ - if (g_logBrowser) - g_logBrowser->outputMessage(type, msg, context.file, context.line); -} - -void SkeletonDocumentWindow::showAcknowlegements() -{ - if (!g_acknowlegementsWidget) { - g_acknowlegementsWidget = new QTextBrowser; - g_acknowlegementsWidget->setWindowTitle(unifiedWindowTitle(tr("Acknowlegements"))); - g_acknowlegementsWidget->setMinimumSize(QSize(400, 300)); - QFile file(":/ACKNOWLEDGEMENTS.html"); - file.open(QFile::ReadOnly | QFile::Text); - QTextStream stream(&file); - g_acknowlegementsWidget->setHtml(stream.readAll()); - } - g_acknowlegementsWidget->show(); - g_acknowlegementsWidget->activateWindow(); - g_acknowlegementsWidget->raise(); -} - -void SkeletonDocumentWindow::showContributors() -{ - if (!g_contributorsWidget) { - g_contributorsWidget = new QTextBrowser; - g_contributorsWidget->setWindowTitle(unifiedWindowTitle(tr("Contributors"))); - g_contributorsWidget->setMinimumSize(QSize(400, 300)); - QFile authors(":/AUTHORS"); - authors.open(QFile::ReadOnly | QFile::Text); - QFile contributors(":/CONTRIBUTORS"); - contributors.open(QFile::ReadOnly | QFile::Text); - g_contributorsWidget->setHtml("

AUTHORS

" + authors.readAll() + "

CONTRIBUTORS

" + contributors.readAll() + "
"); - } - g_contributorsWidget->show(); - g_contributorsWidget->activateWindow(); - g_contributorsWidget->raise(); -} - -void SkeletonDocumentWindow::showAbout() -{ - if (!g_aboutWidget) { - g_aboutWidget = new AboutWidget; - } - g_aboutWidget->show(); - g_aboutWidget->activateWindow(); - g_aboutWidget->raise(); -} - -SkeletonDocumentWindow::SkeletonDocumentWindow() : - m_document(nullptr), - m_firstShow(true), - m_documentSaved(true), - m_exportPreviewWidget(nullptr), - m_advanceSettingWidget(nullptr) -{ - if (!g_logBrowser) { - g_logBrowser = new LogBrowser; - qInstallMessageHandler(&outputMessage); - } - - g_documentWindows.insert(this); - - m_document = new Document; - - QVBoxLayout *toolButtonLayout = new QVBoxLayout; - toolButtonLayout->setSpacing(0); - toolButtonLayout->setContentsMargins(5, 10, 4, 0); - - QPushButton *addButton = new QPushButton(QChar(fa::plus)); - Theme::initAwesomeButton(addButton); - - QPushButton *selectButton = new QPushButton(QChar(fa::mousepointer)); - Theme::initAwesomeButton(selectButton); - - QPushButton *dragButton = new QPushButton(QChar(fa::handrocko)); - Theme::initAwesomeButton(dragButton); - - QPushButton *zoomInButton = new QPushButton(QChar(fa::searchplus)); - Theme::initAwesomeButton(zoomInButton); - - QPushButton *zoomOutButton = new QPushButton(QChar(fa::searchminus)); - Theme::initAwesomeButton(zoomOutButton); - - m_xlockButton = new QPushButton(QChar('X')); - initLockButton(m_xlockButton); - updateXlockButtonState(); - - m_ylockButton = new QPushButton(QChar('Y')); - initLockButton(m_ylockButton); - updateYlockButtonState(); - - m_zlockButton = new QPushButton(QChar('Z')); - initLockButton(m_zlockButton); - updateZlockButtonState(); - - m_radiusLockButton = new QPushButton(QChar(fa::bullseye)); - Theme::initAwesomeButton(m_radiusLockButton); - updateRadiusLockButtonState(); - - QPushButton *rotateCounterclockwiseButton = new QPushButton(QChar(fa::rotateleft)); - Theme::initAwesomeButton(rotateCounterclockwiseButton); - - QPushButton *rotateClockwiseButton = new QPushButton(QChar(fa::rotateright)); - Theme::initAwesomeButton(rotateClockwiseButton); - - SpinnableAwesomeButton *regenerateButton = new SpinnableAwesomeButton(); - regenerateButton->setAwesomeIcon(QChar(fa::recycle)); - connect(m_document, &Document::meshGenerating, this, [=]() { - regenerateButton->showSpinner(true); - }); - connect(m_document, &Document::postProcessing, this, [=]() { - regenerateButton->showSpinner(true); - }); - connect(m_document, &Document::textureGenerating, this, [=]() { - regenerateButton->showSpinner(true); - }); - connect(m_document, &Document::resultTextureChanged, this, [=]() { - regenerateButton->showSpinner(false); - }); - connect(regenerateButton->button(), &QPushButton::clicked, m_document, &Document::regenerateMesh); - - toolButtonLayout->addWidget(addButton); - toolButtonLayout->addWidget(selectButton); - toolButtonLayout->addWidget(dragButton); - toolButtonLayout->addWidget(zoomInButton); - toolButtonLayout->addWidget(zoomOutButton); - toolButtonLayout->addSpacing(10); - toolButtonLayout->addWidget(m_xlockButton); - toolButtonLayout->addWidget(m_ylockButton); - toolButtonLayout->addWidget(m_zlockButton); - toolButtonLayout->addWidget(m_radiusLockButton); - toolButtonLayout->addSpacing(10); - toolButtonLayout->addWidget(rotateCounterclockwiseButton); - toolButtonLayout->addWidget(rotateClockwiseButton); - toolButtonLayout->addSpacing(10); - toolButtonLayout->addWidget(regenerateButton); - - - QLabel *verticalLogoLabel = new QLabel; - QImage verticalLogoImage; - verticalLogoImage.load(":/resources/dust3d-vertical.png"); - verticalLogoLabel->setPixmap(QPixmap::fromImage(verticalLogoImage)); - - QHBoxLayout *logoLayout = new QHBoxLayout; - logoLayout->addWidget(verticalLogoLabel); - logoLayout->setContentsMargins(0, 0, 0, 0); - - QVBoxLayout *mainLeftLayout = new QVBoxLayout; - mainLeftLayout->setSpacing(0); - mainLeftLayout->setContentsMargins(0, 0, 0, 0); - mainLeftLayout->addLayout(toolButtonLayout); - mainLeftLayout->addStretch(); - mainLeftLayout->addLayout(logoLayout); - mainLeftLayout->addSpacing(10); - - SkeletonGraphicsWidget *graphicsWidget = new SkeletonGraphicsWidget(m_document); - m_graphicsWidget = graphicsWidget; - - GraphicsContainerWidget *containerWidget = new GraphicsContainerWidget; - containerWidget->setGraphicsWidget(graphicsWidget); - QGridLayout *containerLayout = new QGridLayout; - containerLayout->setSpacing(0); - containerLayout->setContentsMargins(1, 0, 0, 0); - containerLayout->addWidget(graphicsWidget); - containerWidget->setLayout(containerLayout); - containerWidget->setMinimumSize(400, 400); - - m_modelRenderWidget = new ModelWidget(containerWidget); - m_modelRenderWidget->setMinimumSize(SkeletonDocumentWindow::m_modelRenderWidgetInitialSize, SkeletonDocumentWindow::m_modelRenderWidgetInitialSize); - m_modelRenderWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - m_modelRenderWidget->move(SkeletonDocumentWindow::m_modelRenderWidgetInitialX, SkeletonDocumentWindow::m_modelRenderWidgetInitialY); - - m_graphicsWidget->setModelWidget(m_modelRenderWidget); - - m_document->setSharedContextWidget(m_modelRenderWidget); - - setTabPosition(Qt::RightDockWidgetArea, QTabWidget::East); - - QDockWidget *partTreeDocker = new QDockWidget(tr("Parts"), this); - partTreeDocker->setAllowedAreas(Qt::RightDockWidgetArea); - PartTreeWidget *partTreeWidget = new PartTreeWidget(m_document, partTreeDocker); - partTreeDocker->setWidget(partTreeWidget); - addDockWidget(Qt::RightDockWidgetArea, partTreeDocker); - connect(partTreeDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) { - Q_UNUSED(topLevel); - for (const auto &part: m_document->partMap) - partTreeWidget->partPreviewChanged(part.first); - }); - - QDockWidget *materialDocker = new QDockWidget(tr("Materials"), this); - materialDocker->setAllowedAreas(Qt::RightDockWidgetArea); - MaterialManageWidget *materialManageWidget = new MaterialManageWidget(m_document, materialDocker); - materialDocker->setWidget(materialManageWidget); - connect(materialManageWidget, &MaterialManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog); - connect(materialManageWidget, &MaterialManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog); - addDockWidget(Qt::RightDockWidgetArea, materialDocker); - connect(materialDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) { - Q_UNUSED(topLevel); - for (const auto &material: m_document->materialMap) - emit m_document->materialPreviewChanged(material.first); - }); - - QDockWidget *rigDocker = new QDockWidget(tr("Rig"), this); - rigDocker->setAllowedAreas(Qt::RightDockWidgetArea); - m_rigWidget = new RigWidget(m_document, rigDocker); - rigDocker->setWidget(m_rigWidget); - addDockWidget(Qt::RightDockWidgetArea, rigDocker); - connect(rigDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) { - Q_UNUSED(topLevel); - updateRigWeightRenderWidget(); - }); - - QDockWidget *poseDocker = new QDockWidget(tr("Poses"), this); - poseDocker->setAllowedAreas(Qt::RightDockWidgetArea); - PoseManageWidget *poseManageWidget = new PoseManageWidget(m_document, poseDocker); - poseDocker->setWidget(poseManageWidget); - connect(poseManageWidget, &PoseManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog); - connect(poseManageWidget, &PoseManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog); - addDockWidget(Qt::RightDockWidgetArea, poseDocker); - connect(poseDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) { - Q_UNUSED(topLevel); - for (const auto &pose: m_document->poseMap) - emit m_document->posePreviewChanged(pose.first); - }); - - QDockWidget *motionDocker = new QDockWidget(tr("Motions"), this); - motionDocker->setAllowedAreas(Qt::RightDockWidgetArea); - MotionManageWidget *motionManageWidget = new MotionManageWidget(m_document, motionDocker); - motionDocker->setWidget(motionManageWidget); - connect(motionManageWidget, &MotionManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog); - connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog); - addDockWidget(Qt::RightDockWidgetArea, motionDocker); - - tabifyDockWidget(partTreeDocker, materialDocker); - tabifyDockWidget(materialDocker, rigDocker); - tabifyDockWidget(rigDocker, poseDocker); - tabifyDockWidget(poseDocker, motionDocker); - - partTreeDocker->raise(); - - QHBoxLayout *mainLayout = new QHBoxLayout; - mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->addLayout(mainLeftLayout); - mainLayout->addWidget(containerWidget); - mainLayout->addSpacing(3); - - QWidget *centralWidget = new QWidget; - centralWidget->setLayout(mainLayout); - - setCentralWidget(centralWidget); - setWindowTitle(APP_NAME); - - m_fileMenu = menuBar()->addMenu(tr("File")); - - m_newWindowAction = new QAction(tr("New Window"), this); - connect(m_newWindowAction, &QAction::triggered, this, &SkeletonDocumentWindow::newWindow, Qt::QueuedConnection); - m_fileMenu->addAction(m_newWindowAction); - - m_newDocumentAction = new QAction(tr("New"), this); - connect(m_newDocumentAction, &QAction::triggered, this, &SkeletonDocumentWindow::newDocument); - m_fileMenu->addAction(m_newDocumentAction); - - m_openAction = new QAction(tr("Open..."), this); - connect(m_openAction, &QAction::triggered, this, &SkeletonDocumentWindow::open, Qt::QueuedConnection); - m_fileMenu->addAction(m_openAction); - - m_saveAction = new QAction(tr("Save"), this); - connect(m_saveAction, &QAction::triggered, this, &SkeletonDocumentWindow::save, Qt::QueuedConnection); - m_fileMenu->addAction(m_saveAction); - - m_saveAsAction = new QAction(tr("Save As..."), this); - connect(m_saveAsAction, &QAction::triggered, this, &SkeletonDocumentWindow::saveAs, Qt::QueuedConnection); - m_fileMenu->addAction(m_saveAsAction); - - m_saveAllAction = new QAction(tr("Save All"), this); - connect(m_saveAllAction, &QAction::triggered, this, &SkeletonDocumentWindow::saveAll, Qt::QueuedConnection); - m_fileMenu->addAction(m_saveAllAction); - - m_fileMenu->addSeparator(); - - //m_exportMenu = m_fileMenu->addMenu(tr("Export")); - - m_exportAction = new QAction(tr("Export..."), this); - connect(m_exportAction, &QAction::triggered, this, &SkeletonDocumentWindow::showExportPreview, Qt::QueuedConnection); - m_fileMenu->addAction(m_exportAction); - - m_exportAsObjAction = new QAction(tr("Export as OBJ..."), this); - connect(m_exportAsObjAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportObjResult, Qt::QueuedConnection); - m_fileMenu->addAction(m_exportAsObjAction); - - //m_exportRenderedAsImageAction = new QAction(tr("Export as PNG..."), this); - //connect(m_exportRenderedAsImageAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportRenderedResult, Qt::QueuedConnection); - //m_fileMenu->addAction(m_exportRenderedAsImageAction); - - //m_exportAsObjPlusMaterialsAction = new QAction(tr("Wavefront (.obj + .mtl)..."), this); - //connect(m_exportAsObjPlusMaterialsAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportObjPlusMaterialsResult, Qt::QueuedConnection); - //m_exportMenu->addAction(m_exportAsObjPlusMaterialsAction); - - m_fileMenu->addSeparator(); - - m_changeTurnaroundAction = new QAction(tr("Change Reference Sheet..."), this); - connect(m_changeTurnaroundAction, &QAction::triggered, this, &SkeletonDocumentWindow::changeTurnaround, Qt::QueuedConnection); - m_fileMenu->addAction(m_changeTurnaroundAction); - - m_fileMenu->addSeparator(); - - connect(m_fileMenu, &QMenu::aboutToShow, [=]() { - m_exportAsObjAction->setEnabled(m_graphicsWidget->hasItems()); - //m_exportAsObjPlusMaterialsAction->setEnabled(m_graphicsWidget->hasItems()); - m_exportAction->setEnabled(m_graphicsWidget->hasItems()); - //m_exportRenderedAsImageAction->setEnabled(m_graphicsWidget->hasItems()); - }); - - m_editMenu = menuBar()->addMenu(tr("Edit")); - - m_addAction = new QAction(tr("Add..."), this); - connect(m_addAction, &QAction::triggered, [=]() { - m_document->setEditMode(DocumentEditMode::Add); - }); - m_editMenu->addAction(m_addAction); - - m_undoAction = new QAction(tr("Undo"), this); - connect(m_undoAction, &QAction::triggered, m_document, &Document::undo); - m_editMenu->addAction(m_undoAction); - - m_redoAction = new QAction(tr("Redo"), this); - connect(m_redoAction, &QAction::triggered, m_document, &Document::redo); - m_editMenu->addAction(m_redoAction); - - m_deleteAction = new QAction(tr("Delete"), this); - connect(m_deleteAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::deleteSelected); - m_editMenu->addAction(m_deleteAction); - - m_breakAction = new QAction(tr("Break"), this); - connect(m_breakAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::breakSelected); - m_editMenu->addAction(m_breakAction); - - m_connectAction = new QAction(tr("Connect"), this); - connect(m_connectAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::connectSelected); - m_editMenu->addAction(m_connectAction); - - m_cutAction = new QAction(tr("Cut"), this); - connect(m_cutAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::cut); - m_editMenu->addAction(m_cutAction); - - m_copyAction = new QAction(tr("Copy"), this); - connect(m_copyAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::copy); - m_editMenu->addAction(m_copyAction); - - m_pasteAction = new QAction(tr("Paste"), this); - connect(m_pasteAction, &QAction::triggered, m_document, &Document::paste); - m_editMenu->addAction(m_pasteAction); - - m_flipHorizontallyAction = new QAction(tr("H Flip"), this); - connect(m_flipHorizontallyAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::flipHorizontally); - m_editMenu->addAction(m_flipHorizontallyAction); - - m_flipVerticallyAction = new QAction(tr("V Flip"), this); - connect(m_flipVerticallyAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::flipVertically); - m_editMenu->addAction(m_flipVerticallyAction); - - m_rotateClockwiseAction = new QAction(tr("Rotate 90D CW"), this); - connect(m_rotateClockwiseAction, &QAction::triggered, [=] { - m_graphicsWidget->rotateClockwise90Degree(); - }); - m_editMenu->addAction(m_rotateClockwiseAction); - - m_rotateCounterclockwiseAction = new QAction(tr("Rotate 90D CCW"), this); - connect(m_rotateCounterclockwiseAction, &QAction::triggered, [=] { - m_graphicsWidget->rotateCounterclockwise90Degree(); - }); - m_editMenu->addAction(m_rotateCounterclockwiseAction); - - m_switchXzAction = new QAction(tr("Switch XZ"), this); - connect(m_switchXzAction, &QAction::triggered, [=] { - m_graphicsWidget->switchSelectedXZ(); - }); - m_editMenu->addAction(m_switchXzAction); - - m_alignToMenu = new QMenu(tr("Align To")); - - m_alignToLocalCenterAction = new QAction(tr("Local Center"), this); - connect(m_alignToLocalCenterAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::alignSelectedToLocalCenter); - m_alignToMenu->addAction(m_alignToLocalCenterAction); - - m_alignToLocalVerticalCenterAction = new QAction(tr("Local Vertical Center"), this); - connect(m_alignToLocalVerticalCenterAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::alignSelectedToLocalVerticalCenter); - m_alignToMenu->addAction(m_alignToLocalVerticalCenterAction); - - m_alignToLocalHorizontalCenterAction = new QAction(tr("Local Horizontal Center"), this); - connect(m_alignToLocalHorizontalCenterAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::alignSelectedToLocalHorizontalCenter); - m_alignToMenu->addAction(m_alignToLocalHorizontalCenterAction); - - m_alignToGlobalCenterAction = new QAction(tr("Global Center"), this); - connect(m_alignToGlobalCenterAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::alignSelectedToGlobalCenter); - m_alignToMenu->addAction(m_alignToGlobalCenterAction); - - m_alignToGlobalVerticalCenterAction = new QAction(tr("Global Vertical Center"), this); - connect(m_alignToGlobalVerticalCenterAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::alignSelectedToGlobalVerticalCenter); - m_alignToMenu->addAction(m_alignToGlobalVerticalCenterAction); - - m_alignToGlobalHorizontalCenterAction = new QAction(tr("Global Horizontal Center"), this); - connect(m_alignToGlobalHorizontalCenterAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::alignSelectedToGlobalHorizontalCenter); - m_alignToMenu->addAction(m_alignToGlobalHorizontalCenterAction); - - m_editMenu->addMenu(m_alignToMenu); - - m_markAsMenu = new QMenu(tr("Mark As")); - - m_markAsNoneAction = new QAction(tr("None"), this); - connect(m_markAsNoneAction, &QAction::triggered, [=]() { - m_graphicsWidget->setSelectedNodesBoneMark(BoneMark::None); - }); - m_markAsMenu->addAction(m_markAsNoneAction); - - m_markAsMenu->addSeparator(); - - for (int i = 0; i < (int)BoneMark::Count - 1; i++) { - BoneMark boneMark = (BoneMark)(i + 1); - m_markAsActions[i] = new QAction(MarkIconCreator::createIcon(boneMark), BoneMarkToDispName(boneMark), this); - connect(m_markAsActions[i], &QAction::triggered, [=]() { - m_graphicsWidget->setSelectedNodesBoneMark(boneMark); - }); - m_markAsMenu->addAction(m_markAsActions[i]); - } - - m_editMenu->addMenu(m_markAsMenu); - - m_selectAllAction = new QAction(tr("Select All"), this); - connect(m_selectAllAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::selectAll); - m_editMenu->addAction(m_selectAllAction); - - m_selectPartAllAction = new QAction(tr("Select Part"), this); - connect(m_selectPartAllAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::selectPartAll); - m_editMenu->addAction(m_selectPartAllAction); - - m_unselectAllAction = new QAction(tr("Unselect All"), this); - connect(m_unselectAllAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::unselectAll); - m_editMenu->addAction(m_unselectAllAction); - - connect(m_editMenu, &QMenu::aboutToShow, [=]() { - m_undoAction->setEnabled(m_document->undoable()); - m_redoAction->setEnabled(m_document->redoable()); - m_deleteAction->setEnabled(m_graphicsWidget->hasSelection()); - m_breakAction->setEnabled(m_graphicsWidget->hasEdgeSelection()); - m_connectAction->setEnabled(m_graphicsWidget->hasTwoDisconnectedNodesSelection()); - m_cutAction->setEnabled(m_graphicsWidget->hasSelection()); - m_copyAction->setEnabled(m_graphicsWidget->hasSelection()); - m_pasteAction->setEnabled(m_document->hasPastableNodesInClipboard()); - m_flipHorizontallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection()); - m_flipVerticallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection()); - m_rotateClockwiseAction->setEnabled(m_graphicsWidget->hasMultipleSelection()); - m_rotateCounterclockwiseAction->setEnabled(m_graphicsWidget->hasMultipleSelection()); - m_switchXzAction->setEnabled(m_graphicsWidget->hasSelection()); - m_alignToGlobalCenterAction->setEnabled(m_graphicsWidget->hasSelection() && m_document->originSettled()); - m_alignToGlobalVerticalCenterAction->setEnabled(m_graphicsWidget->hasSelection() && m_document->originSettled()); - m_alignToGlobalHorizontalCenterAction->setEnabled(m_graphicsWidget->hasSelection() && m_document->originSettled()); - m_alignToLocalCenterAction->setEnabled(m_graphicsWidget->hasMultipleSelection()); - m_alignToLocalVerticalCenterAction->setEnabled(m_graphicsWidget->hasMultipleSelection()); - m_alignToLocalHorizontalCenterAction->setEnabled(m_graphicsWidget->hasMultipleSelection()); - m_alignToMenu->setEnabled((m_graphicsWidget->hasSelection() && m_document->originSettled()) || m_graphicsWidget->hasMultipleSelection()); - m_selectAllAction->setEnabled(m_graphicsWidget->hasItems()); - m_selectPartAllAction->setEnabled(m_graphicsWidget->hasItems()); - m_unselectAllAction->setEnabled(m_graphicsWidget->hasSelection()); - }); - - m_viewMenu = menuBar()->addMenu(tr("View")); - - auto isModelSitInVisibleArea = [](ModelWidget *modelWidget) { - QRect parentRect = QRect(QPoint(0, 0), modelWidget->parentWidget()->size()); - return parentRect.contains(modelWidget->geometry().center()); - }; - - m_resetModelWidgetPosAction = new QAction(tr("Show Model"), this); - connect(m_resetModelWidgetPosAction, &QAction::triggered, [=]() { - if (!isModelSitInVisibleArea(m_modelRenderWidget)) { - m_modelRenderWidget->move(SkeletonDocumentWindow::m_modelRenderWidgetInitialX, SkeletonDocumentWindow::m_modelRenderWidgetInitialY); - } - }); - m_viewMenu->addAction(m_resetModelWidgetPosAction); - - //m_toggleWireframeAction = new QAction(tr("Toggle Wireframe"), this); - //connect(m_toggleWireframeAction, &QAction::triggered, [=]() { - // m_modelRenderWidget->toggleWireframe(); - //}); - //m_viewMenu->addAction(m_toggleWireframeAction); - - //m_toggleSmoothNormalAction = new QAction(tr("Toggle Smooth Normal"), this); - //connect(m_toggleSmoothNormalAction, &QAction::triggered, [=]() { - // m_document->toggleSmoothNormal(); - //}); - //m_viewMenu->addAction(m_toggleSmoothNormalAction); - - connect(m_viewMenu, &QMenu::aboutToShow, [=]() { - m_resetModelWidgetPosAction->setEnabled(!isModelSitInVisibleArea(m_modelRenderWidget)); - }); - - m_windowMenu = menuBar()->addMenu(tr("Window")); - - m_showPartsListAction = new QAction(tr("Parts"), this); - connect(m_showPartsListAction, &QAction::triggered, [=]() { - partTreeDocker->show(); - partTreeDocker->raise(); - }); - m_windowMenu->addAction(m_showPartsListAction); - - m_showMaterialsAction = new QAction(tr("Materials"), this); - connect(m_showMaterialsAction, &QAction::triggered, [=]() { - materialDocker->show(); - materialDocker->raise(); - }); - m_windowMenu->addAction(m_showMaterialsAction); - - m_showRigAction = new QAction(tr("Rig"), this); - connect(m_showRigAction, &QAction::triggered, [=]() { - rigDocker->show(); - rigDocker->raise(); - }); - m_windowMenu->addAction(m_showRigAction); - - m_showPosesAction = new QAction(tr("Poses"), this); - connect(m_showPosesAction, &QAction::triggered, [=]() { - poseDocker->show(); - poseDocker->raise(); - }); - m_windowMenu->addAction(m_showPosesAction); - - m_showMotionsAction = new QAction(tr("Motions"), this); - connect(m_showMotionsAction, &QAction::triggered, [=]() { - motionDocker->show(); - motionDocker->raise(); - }); - m_windowMenu->addAction(m_showMotionsAction); - - QMenu *dialogsMenu = m_windowMenu->addMenu(tr("Dialogs")); - connect(dialogsMenu, &QMenu::aboutToShow, [=]() { - dialogsMenu->clear(); - if (this->m_dialogs.empty()) { - QAction *action = dialogsMenu->addAction(tr("None")); - action->setEnabled(false); - return; - } - for (const auto &dialog: this->m_dialogs) { - QAction *action = dialogsMenu->addAction(dialog->windowTitle()); - connect(action, &QAction::triggered, [=]() { - dialog->show(); - dialog->raise(); - }); - } - }); - - m_showDebugDialogAction = new QAction(tr("Debug"), this); - connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog); - m_windowMenu->addAction(m_showDebugDialogAction); - - m_showAdvanceSettingAction = new QAction(tr("Advance"), this); - connect(m_showAdvanceSettingAction, &QAction::triggered, this, &SkeletonDocumentWindow::showAdvanceSetting); -#ifndef NDEBUG - m_windowMenu->addAction(m_showAdvanceSettingAction); -#endif - - m_helpMenu = menuBar()->addMenu(tr("Help")); - - m_viewSourceAction = new QAction(tr("Fork me on GitHub"), this); - connect(m_viewSourceAction, &QAction::triggered, this, &SkeletonDocumentWindow::viewSource); - m_helpMenu->addAction(m_viewSourceAction); - - m_helpMenu->addSeparator(); - - m_seeReferenceGuideAction = new QAction(tr("Reference Guide"), this); - connect(m_seeReferenceGuideAction, &QAction::triggered, this, &SkeletonDocumentWindow::seeReferenceGuide); - m_helpMenu->addAction(m_seeReferenceGuideAction); - - m_helpMenu->addSeparator(); - - m_aboutAction = new QAction(tr("About"), this); - connect(m_aboutAction, &QAction::triggered, this, &SkeletonDocumentWindow::about); - m_helpMenu->addAction(m_aboutAction); - - m_reportIssuesAction = new QAction(tr("Report Issues"), this); - connect(m_reportIssuesAction, &QAction::triggered, this, &SkeletonDocumentWindow::reportIssues); - m_helpMenu->addAction(m_reportIssuesAction); - - m_helpMenu->addSeparator(); - - m_seeContributorsAction = new QAction(tr("Contributors"), this); - connect(m_seeContributorsAction, &QAction::triggered, this, &SkeletonDocumentWindow::seeContributors); - m_helpMenu->addAction(m_seeContributorsAction); - - m_seeAcknowlegementsAction = new QAction(tr("Acknowlegements"), this); - connect(m_seeAcknowlegementsAction, &QAction::triggered, this, &SkeletonDocumentWindow::seeAcknowlegements); - m_helpMenu->addAction(m_seeAcknowlegementsAction); - - connect(containerWidget, &GraphicsContainerWidget::containerSizeChanged, - graphicsWidget, &SkeletonGraphicsWidget::canvasResized); - - connect(m_document, &Document::turnaroundChanged, - graphicsWidget, &SkeletonGraphicsWidget::turnaroundChanged); - - connect(rotateCounterclockwiseButton, &QPushButton::clicked, graphicsWidget, &SkeletonGraphicsWidget::rotateAllMainProfileCounterclockwise90DegreeAlongOrigin); - connect(rotateClockwiseButton, &QPushButton::clicked, graphicsWidget, &SkeletonGraphicsWidget::rotateAllMainProfileClockwise90DegreeAlongOrigin); - - connect(addButton, &QPushButton::clicked, [=]() { - m_document->setEditMode(DocumentEditMode::Add); - }); - - connect(selectButton, &QPushButton::clicked, [=]() { - m_document->setEditMode(DocumentEditMode::Select); - }); - - connect(dragButton, &QPushButton::clicked, [=]() { - m_document->setEditMode(DocumentEditMode::Drag); - }); - - connect(zoomInButton, &QPushButton::clicked, [=]() { - m_document->setEditMode(DocumentEditMode::ZoomIn); - }); - - connect(zoomOutButton, &QPushButton::clicked, [=]() { - m_document->setEditMode(DocumentEditMode::ZoomOut); - }); - - connect(m_xlockButton, &QPushButton::clicked, [=]() { - m_document->setXlockState(!m_document->xlocked); - }); - connect(m_ylockButton, &QPushButton::clicked, [=]() { - m_document->setYlockState(!m_document->ylocked); - }); - connect(m_zlockButton, &QPushButton::clicked, [=]() { - m_document->setZlockState(!m_document->zlocked); - }); - connect(m_radiusLockButton, &QPushButton::clicked, [=]() { - m_document->setRadiusLockState(!m_document->radiusLocked); - }); - - m_partListDockerVisibleSwitchConnection = connect(m_document, &Document::skeletonChanged, [=]() { - if (m_graphicsWidget->hasItems()) { - if (partTreeDocker->isHidden()) - partTreeDocker->show(); - disconnect(m_partListDockerVisibleSwitchConnection); - } - }); - - connect(m_document, &Document::editModeChanged, graphicsWidget, &SkeletonGraphicsWidget::editModeChanged); - - connect(graphicsWidget, &SkeletonGraphicsWidget::zoomRenderedModelBy, m_modelRenderWidget, &ModelWidget::zoom); - - connect(graphicsWidget, &SkeletonGraphicsWidget::addNode, m_document, &Document::addNode); - connect(graphicsWidget, &SkeletonGraphicsWidget::scaleNodeByAddRadius, m_document, &Document::scaleNodeByAddRadius); - connect(graphicsWidget, &SkeletonGraphicsWidget::moveNodeBy, m_document, &Document::moveNodeBy); - connect(graphicsWidget, &SkeletonGraphicsWidget::setNodeOrigin, m_document, &Document::setNodeOrigin); - connect(graphicsWidget, &SkeletonGraphicsWidget::setNodeBoneMark, m_document, &Document::setNodeBoneMark); - connect(graphicsWidget, &SkeletonGraphicsWidget::removeNode, m_document, &Document::removeNode); - connect(graphicsWidget, &SkeletonGraphicsWidget::setEditMode, m_document, &Document::setEditMode); - connect(graphicsWidget, &SkeletonGraphicsWidget::removeEdge, m_document, &Document::removeEdge); - connect(graphicsWidget, &SkeletonGraphicsWidget::addEdge, m_document, &Document::addEdge); - connect(graphicsWidget, &SkeletonGraphicsWidget::groupOperationAdded, m_document, &Document::saveSnapshot); - connect(graphicsWidget, &SkeletonGraphicsWidget::undo, m_document, &Document::undo); - connect(graphicsWidget, &SkeletonGraphicsWidget::redo, m_document, &Document::redo); - connect(graphicsWidget, &SkeletonGraphicsWidget::paste, m_document, &Document::paste); - connect(graphicsWidget, &SkeletonGraphicsWidget::batchChangeBegin, m_document, &Document::batchChangeBegin); - connect(graphicsWidget, &SkeletonGraphicsWidget::batchChangeEnd, m_document, &Document::batchChangeEnd); - connect(graphicsWidget, &SkeletonGraphicsWidget::breakEdge, m_document, &Document::breakEdge); - connect(graphicsWidget, &SkeletonGraphicsWidget::moveOriginBy, m_document, &Document::moveOriginBy); - connect(graphicsWidget, &SkeletonGraphicsWidget::partChecked, m_document, &Document::partChecked); - connect(graphicsWidget, &SkeletonGraphicsWidget::partUnchecked, m_document, &Document::partUnchecked); - connect(graphicsWidget, &SkeletonGraphicsWidget::switchNodeXZ, m_document, &Document::switchNodeXZ); - - connect(graphicsWidget, &SkeletonGraphicsWidget::setPartLockState, m_document, &Document::setPartLockState); - connect(graphicsWidget, &SkeletonGraphicsWidget::setPartVisibleState, m_document, &Document::setPartVisibleState); - connect(graphicsWidget, &SkeletonGraphicsWidget::setPartSubdivState, m_document, &Document::setPartSubdivState); - connect(graphicsWidget, &SkeletonGraphicsWidget::setPartDisableState, m_document, &Document::setPartDisableState); - connect(graphicsWidget, &SkeletonGraphicsWidget::setPartXmirrorState, m_document, &Document::setPartXmirrorState); - connect(graphicsWidget, &SkeletonGraphicsWidget::setPartRoundState, m_document, &Document::setPartRoundState); - connect(graphicsWidget, &SkeletonGraphicsWidget::setPartWrapState, m_document, &Document::setPartWrapState); - - connect(graphicsWidget, &SkeletonGraphicsWidget::setXlockState, m_document, &Document::setXlockState); - connect(graphicsWidget, &SkeletonGraphicsWidget::setYlockState, m_document, &Document::setYlockState); - connect(graphicsWidget, &SkeletonGraphicsWidget::setZlockState, m_document, &Document::setZlockState); - - connect(graphicsWidget, &SkeletonGraphicsWidget::enableAllPositionRelatedLocks, m_document, &Document::enableAllPositionRelatedLocks); - connect(graphicsWidget, &SkeletonGraphicsWidget::disableAllPositionRelatedLocks, m_document, &Document::disableAllPositionRelatedLocks); - - connect(graphicsWidget, &SkeletonGraphicsWidget::changeTurnaround, this, &SkeletonDocumentWindow::changeTurnaround); - connect(graphicsWidget, &SkeletonGraphicsWidget::save, this, &SkeletonDocumentWindow::save); - connect(graphicsWidget, &SkeletonGraphicsWidget::open, this, &SkeletonDocumentWindow::open); - - connect(m_document, &Document::optionsChanged, m_document, &Document::saveSnapshot); - - connect(m_document, &Document::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded); - connect(m_document, &Document::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved); - connect(m_document, &Document::edgeAdded, graphicsWidget, &SkeletonGraphicsWidget::edgeAdded); - connect(m_document, &Document::edgeRemoved, graphicsWidget, &SkeletonGraphicsWidget::edgeRemoved); - connect(m_document, &Document::nodeRadiusChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeRadiusChanged); - connect(m_document, &Document::nodeBoneMarkChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeBoneMarkChanged); - connect(m_document, &Document::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged); - connect(m_document, &Document::partVisibleStateChanged, graphicsWidget, &SkeletonGraphicsWidget::partVisibleStateChanged); - connect(m_document, &Document::partDisableStateChanged, graphicsWidget, &SkeletonGraphicsWidget::partVisibleStateChanged); - connect(m_document, &Document::cleanup, graphicsWidget, &SkeletonGraphicsWidget::removeAllContent); - connect(m_document, &Document::originChanged, graphicsWidget, &SkeletonGraphicsWidget::originChanged); - connect(m_document, &Document::checkPart, graphicsWidget, &SkeletonGraphicsWidget::selectPartAllById); - connect(m_document, &Document::enableBackgroundBlur, graphicsWidget, &SkeletonGraphicsWidget::enableBackgroundBlur); - connect(m_document, &Document::disableBackgroundBlur, graphicsWidget, &SkeletonGraphicsWidget::disableBackgroundBlur); - connect(m_document, &Document::uncheckAll, graphicsWidget, &SkeletonGraphicsWidget::unselectAll); - connect(m_document, &Document::checkNode, graphicsWidget, &SkeletonGraphicsWidget::addSelectNode); - connect(m_document, &Document::checkEdge, graphicsWidget, &SkeletonGraphicsWidget::addSelectEdge); - - connect(partTreeWidget, &PartTreeWidget::currentComponentChanged, m_document, &Document::setCurrentCanvasComponentId); - connect(partTreeWidget, &PartTreeWidget::moveComponentUp, m_document, &Document::moveComponentUp); - connect(partTreeWidget, &PartTreeWidget::moveComponentDown, m_document, &Document::moveComponentDown); - connect(partTreeWidget, &PartTreeWidget::moveComponentToTop, m_document, &Document::moveComponentToTop); - connect(partTreeWidget, &PartTreeWidget::moveComponentToBottom, m_document, &Document::moveComponentToBottom); - connect(partTreeWidget, &PartTreeWidget::checkPart, m_document, &Document::checkPart); - connect(partTreeWidget, &PartTreeWidget::createNewComponentAndMoveThisIn, m_document, &Document::createNewComponentAndMoveThisIn); - connect(partTreeWidget, &PartTreeWidget::createNewChildComponent, m_document, &Document::createNewChildComponent); - connect(partTreeWidget, &PartTreeWidget::renameComponent, m_document, &Document::renameComponent); - connect(partTreeWidget, &PartTreeWidget::setComponentExpandState, m_document, &Document::setComponentExpandState); - connect(partTreeWidget, &PartTreeWidget::setComponentSmoothAll, m_document, &Document::setComponentSmoothAll); - connect(partTreeWidget, &PartTreeWidget::setComponentSmoothSeam, m_document, &Document::setComponentSmoothSeam); - connect(partTreeWidget, &PartTreeWidget::moveComponent, m_document, &Document::moveComponent); - connect(partTreeWidget, &PartTreeWidget::removeComponent, m_document, &Document::removeComponent); - connect(partTreeWidget, &PartTreeWidget::hideOtherComponents, m_document, &Document::hideOtherComponents); - connect(partTreeWidget, &PartTreeWidget::lockOtherComponents, m_document, &Document::lockOtherComponents); - connect(partTreeWidget, &PartTreeWidget::hideAllComponents, m_document, &Document::hideAllComponents); - connect(partTreeWidget, &PartTreeWidget::showAllComponents, m_document, &Document::showAllComponents); - connect(partTreeWidget, &PartTreeWidget::collapseAllComponents, m_document, &Document::collapseAllComponents); - connect(partTreeWidget, &PartTreeWidget::expandAllComponents, m_document, &Document::expandAllComponents); - connect(partTreeWidget, &PartTreeWidget::lockAllComponents, m_document, &Document::lockAllComponents); - connect(partTreeWidget, &PartTreeWidget::unlockAllComponents, m_document, &Document::unlockAllComponents); - connect(partTreeWidget, &PartTreeWidget::setPartLockState, m_document, &Document::setPartLockState); - connect(partTreeWidget, &PartTreeWidget::setPartVisibleState, m_document, &Document::setPartVisibleState); - connect(partTreeWidget, &PartTreeWidget::setComponentInverseState, m_document, &Document::setComponentInverseState); - connect(partTreeWidget, &PartTreeWidget::hideDescendantComponents, m_document, &Document::hideDescendantComponents); - connect(partTreeWidget, &PartTreeWidget::showDescendantComponents, m_document, &Document::showDescendantComponents); - connect(partTreeWidget, &PartTreeWidget::lockDescendantComponents, m_document, &Document::lockDescendantComponents); - connect(partTreeWidget, &PartTreeWidget::unlockDescendantComponents, m_document, &Document::unlockDescendantComponents); - - connect(partTreeWidget, &PartTreeWidget::addPartToSelection, graphicsWidget, &SkeletonGraphicsWidget::addPartToSelection); - - connect(m_document, &Document::componentNameChanged, partTreeWidget, &PartTreeWidget::componentNameChanged); - connect(m_document, &Document::componentChildrenChanged, partTreeWidget, &PartTreeWidget::componentChildrenChanged); - connect(m_document, &Document::componentRemoved, partTreeWidget, &PartTreeWidget::componentRemoved); - connect(m_document, &Document::componentAdded, partTreeWidget, &PartTreeWidget::componentAdded); - connect(m_document, &Document::componentExpandStateChanged, partTreeWidget, &PartTreeWidget::componentExpandStateChanged); - connect(m_document, &Document::partPreviewChanged, partTreeWidget, &PartTreeWidget::partPreviewChanged); - connect(m_document, &Document::partLockStateChanged, partTreeWidget, &PartTreeWidget::partLockStateChanged); - connect(m_document, &Document::partVisibleStateChanged, partTreeWidget, &PartTreeWidget::partVisibleStateChanged); - connect(m_document, &Document::partSubdivStateChanged, partTreeWidget, &PartTreeWidget::partSubdivStateChanged); - connect(m_document, &Document::partDisableStateChanged, partTreeWidget, &PartTreeWidget::partDisableStateChanged); - connect(m_document, &Document::partXmirrorStateChanged, partTreeWidget, &PartTreeWidget::partXmirrorStateChanged); - connect(m_document, &Document::partDeformThicknessChanged, partTreeWidget, &PartTreeWidget::partDeformChanged); - connect(m_document, &Document::partDeformWidthChanged, partTreeWidget, &PartTreeWidget::partDeformChanged); - connect(m_document, &Document::partRoundStateChanged, partTreeWidget, &PartTreeWidget::partRoundStateChanged); - connect(m_document, &Document::partWrapStateChanged, partTreeWidget, &PartTreeWidget::partWrapStateChanged); - connect(m_document, &Document::partColorStateChanged, partTreeWidget, &PartTreeWidget::partColorStateChanged); - connect(m_document, &Document::partMaterialIdChanged, partTreeWidget, &PartTreeWidget::partMaterialIdChanged); - connect(m_document, &Document::partRemoved, partTreeWidget, &PartTreeWidget::partRemoved); - connect(m_document, &Document::cleanup, partTreeWidget, &PartTreeWidget::removeAllContent); - connect(m_document, &Document::partChecked, partTreeWidget, &PartTreeWidget::partChecked); - connect(m_document, &Document::partUnchecked, partTreeWidget, &PartTreeWidget::partUnchecked); - - connect(m_document, &Document::skeletonChanged, m_document, &Document::generateMesh); - //connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() { - // if ((m_exportPreviewWidget && m_exportPreviewWidget->isVisible())) { - // m_document->postProcess(); - // } - //}); - //connect(m_document, &SkeletonDocument::textureChanged, [=]() { - // if ((m_exportPreviewWidget && m_exportPreviewWidget->isVisible())) { - // m_document->generateTexture(); - // } - //}); - connect(m_document, &Document::textureChanged, m_document, &Document::generateTexture); - connect(m_document, &Document::resultMeshChanged, m_document, &Document::postProcess); - connect(m_document, &Document::resultMeshChanged, m_document, &Document::generateRig); - connect(m_document, &Document::rigChanged, m_document, &Document::generateRig); - connect(m_document, &Document::postProcessedResultChanged, m_document, &Document::generateTexture); - //connect(m_document, &SkeletonDocument::resultTextureChanged, m_document, &SkeletonDocument::bakeAmbientOcclusionTexture); - connect(m_document, &Document::resultTextureChanged, [=]() { - if (m_document->isMeshGenerating()) - return; - m_modelRenderWidget->updateMesh(m_document->takeResultTextureMesh()); - }); - - connect(m_document, &Document::resultMeshChanged, [=]() { - m_modelRenderWidget->updateMesh(m_document->takeResultMesh()); - }); - - connect(m_document, &Document::posesChanged, m_document, &Document::generateMotions); - connect(m_document, &Document::motionsChanged, m_document, &Document::generateMotions); - - connect(graphicsWidget, &SkeletonGraphicsWidget::cursorChanged, [=]() { - m_modelRenderWidget->setCursor(graphicsWidget->cursor()); - //m_skeletonRenderWidget->setCursor(graphicsWidget->cursor()); - }); - - connect(m_document, &Document::skeletonChanged, this, &SkeletonDocumentWindow::documentChanged); - connect(m_document, &Document::turnaroundChanged, this, &SkeletonDocumentWindow::documentChanged); - connect(m_document, &Document::optionsChanged, this, &SkeletonDocumentWindow::documentChanged); - connect(m_document, &Document::rigChanged, this, &SkeletonDocumentWindow::documentChanged); - - connect(m_modelRenderWidget, &ModelWidget::customContextMenuRequested, [=](const QPoint &pos) { - graphicsWidget->showContextMenu(graphicsWidget->mapFromGlobal(m_modelRenderWidget->mapToGlobal(pos))); - }); - - connect(m_document, &Document::xlockStateChanged, this, &SkeletonDocumentWindow::updateXlockButtonState); - connect(m_document, &Document::ylockStateChanged, this, &SkeletonDocumentWindow::updateYlockButtonState); - connect(m_document, &Document::zlockStateChanged, this, &SkeletonDocumentWindow::updateZlockButtonState); - connect(m_document, &Document::radiusLockStateChanged, this, &SkeletonDocumentWindow::updateRadiusLockButtonState); - - connect(m_rigWidget, &RigWidget::setRigType, m_document, &Document::setRigType); - - connect(m_document, &Document::rigTypeChanged, m_rigWidget, &RigWidget::rigTypeChanged); - connect(m_document, &Document::resultRigChanged, m_rigWidget, &RigWidget::updateResultInfo); - connect(m_document, &Document::resultRigChanged, this, &SkeletonDocumentWindow::updateRigWeightRenderWidget); - - //connect(m_document, &SkeletonDocument::resultRigChanged, tetrapodPoseEditWidget, &TetrapodPoseEditWidget::updatePreview); - - connect(m_document, &Document::poseAdded, this, [=](QUuid poseId) { - Q_UNUSED(poseId); - m_document->generatePosePreviews(); - }); - connect(m_document, &Document::poseParametersChanged, this, [=](QUuid poseId) { - Q_UNUSED(poseId); - m_document->generatePosePreviews(); - }); - connect(m_document, &Document::resultRigChanged, m_document, &Document::generatePosePreviews); - - connect(m_document, &Document::resultRigChanged, m_document, &Document::generateMotions); - - connect(m_document, &Document::materialAdded, this, [=](QUuid materialId) { - Q_UNUSED(materialId); - m_document->generateMaterialPreviews(); - }); - connect(m_document, &Document::materialLayersChanged, this, [=](QUuid materialId) { - Q_UNUSED(materialId); - m_document->generateMaterialPreviews(); - }); - - initShortCuts(this, m_graphicsWidget); - - connect(this, &SkeletonDocumentWindow::initialized, m_document, &Document::uiReady); - - QTimer *timer = new QTimer(this); - timer->setInterval(250); - connect(timer, &QTimer::timeout, [=] { - QWidget *focusedWidget = QApplication::focusWidget(); - //qDebug() << (focusedWidget ? ("Focused on:" + QString(focusedWidget->metaObject()->className()) + " title:" + focusedWidget->windowTitle()) : "No Focus") << " isActive:" << isActiveWindow(); - if (nullptr == focusedWidget && isActiveWindow()) - graphicsWidget->setFocus(); - }); - timer->start(); -} - -SkeletonDocumentWindow *SkeletonDocumentWindow::createDocumentWindow() -{ - SkeletonDocumentWindow *documentWindow = new SkeletonDocumentWindow(); - documentWindow->setAttribute(Qt::WA_DeleteOnClose); - documentWindow->showMaximized(); - return documentWindow; -} - -void SkeletonDocumentWindow::closeEvent(QCloseEvent *event) -{ - if (m_documentSaved) { - event->accept(); - return; - } - - QMessageBox::StandardButton answer = QMessageBox::question(this, - APP_NAME, - tr("Do you really want to close while there are unsaved changes?"), - QMessageBox::Yes | QMessageBox::No); - if (answer == QMessageBox::Yes) - event->accept(); - else - event->ignore(); -} - -void SkeletonDocumentWindow::setCurrentFilename(const QString &filename) -{ - m_currentFilename = filename; - m_documentSaved = true; - updateTitle(); -} - -void SkeletonDocumentWindow::updateTitle() -{ - QString appName = APP_NAME; - QString appVer = APP_HUMAN_VER; - setWindowTitle(QString("%1 %2 %3%4").arg(appName).arg(appVer).arg(m_currentFilename).arg(m_documentSaved ? "" : "*")); -} - -void SkeletonDocumentWindow::documentChanged() -{ - if (m_documentSaved) { - m_documentSaved = false; - updateTitle(); - } -} - -void SkeletonDocumentWindow::newWindow() -{ - SkeletonDocumentWindow::createDocumentWindow(); -} - -void SkeletonDocumentWindow::newDocument() -{ - if (!m_documentSaved) { - QMessageBox::StandardButton answer = QMessageBox::question(this, - APP_NAME, - tr("Do you really want to create new document and lose the unsaved changes?"), - QMessageBox::Yes | QMessageBox::No); - if (answer != QMessageBox::Yes) - return; - } - m_document->reset(); -} - -void SkeletonDocumentWindow::saveAs() -{ - QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), - tr("Dust3D Document (*.ds3)")); - if (filename.isEmpty()) { - return; - } - saveTo(filename); -} - -void SkeletonDocumentWindow::saveAll() -{ - for (auto &window: g_documentWindows) { - window->save(); - } -} - -void SkeletonDocumentWindow::viewSource() -{ - QString url = APP_REPOSITORY_URL; - qDebug() << "viewSource:" << url; - QDesktopServices::openUrl(QUrl(url)); -} - -void SkeletonDocumentWindow::about() -{ - SkeletonDocumentWindow::showAbout(); -} - -void SkeletonDocumentWindow::reportIssues() -{ - QString url = APP_ISSUES_URL; - qDebug() << "reportIssues:" << url; - QDesktopServices::openUrl(QUrl(url)); -} - -void SkeletonDocumentWindow::seeReferenceGuide() -{ - QString url = APP_REFERENCE_GUIDE_URL; - qDebug() << "referenceGuide:" << url; - QDesktopServices::openUrl(QUrl(url)); -} - -void SkeletonDocumentWindow::seeAcknowlegements() -{ - SkeletonDocumentWindow::showAcknowlegements(); -} - -void SkeletonDocumentWindow::seeContributors() -{ - SkeletonDocumentWindow::showContributors(); -} - -void SkeletonDocumentWindow::initLockButton(QPushButton *button) -{ - QFont font; - font.setWeight(QFont::Light); - font.setPixelSize(Theme::toolIconFontSize); - font.setBold(false); - - button->setFont(font); - button->setFixedSize(Theme::toolIconSize, Theme::toolIconSize); - button->setStyleSheet("QPushButton {color: #f7d9c8}"); - button->setFocusPolicy(Qt::NoFocus); -} - -SkeletonDocumentWindow::~SkeletonDocumentWindow() -{ - g_documentWindows.erase(this); -} - -void SkeletonDocumentWindow::showEvent(QShowEvent *event) -{ - QMainWindow::showEvent(event); - if (m_firstShow) { - m_firstShow = false; - updateTitle(); - m_document->silentReset(); - m_graphicsWidget->setFocus(); - emit initialized(); - } -} - -void SkeletonDocumentWindow::mousePressEvent(QMouseEvent *event) -{ - QMainWindow::mousePressEvent(event); -} - -void SkeletonDocumentWindow::changeTurnaround() -{ - QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(), - tr("Image Files (*.png *.jpg *.bmp)")).trimmed(); - if (fileName.isEmpty()) - return; - QImage image; - if (!image.load(fileName)) - return; - m_document->updateTurnaround(image); -} - -void SkeletonDocumentWindow::save() -{ - saveTo(m_currentFilename); -} - -void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename) -{ - QString filename = saveAsFilename; - - if (filename.isEmpty()) { - filename = QFileDialog::getSaveFileName(this, QString(), QString(), - tr("Dust3D Document (*.ds3)")); - if (filename.isEmpty()) { - return; - } - } - - QApplication::setOverrideCursor(Qt::WaitCursor); - - Ds3FileWriter ds3Writer; - - QByteArray modelXml; - QXmlStreamWriter stream(&modelXml); - Snapshot snapshot; - m_document->toSnapshot(&snapshot); - saveSkeletonToXmlStream(&snapshot, &stream); - if (modelXml.size() > 0) - ds3Writer.add("model.xml", "model", &modelXml); - - QByteArray imageByteArray; - QBuffer pngBuffer(&imageByteArray); - if (!m_document->turnaround.isNull()) { - pngBuffer.open(QIODevice::WriteOnly); - m_document->turnaround.save(&pngBuffer, "PNG"); - if (imageByteArray.size() > 0) - ds3Writer.add("canvas.png", "asset", &imageByteArray); - } - - for (auto &material: snapshot.materials) { - for (auto &layer: material.second) { - for (auto &mapItem: layer.second) { - auto findImageIdString = mapItem.find("linkData"); - if (findImageIdString == mapItem.end()) - continue; - QUuid imageId = QUuid(findImageIdString->second); - const QImage *image = ImageForever::get(imageId); - if (nullptr == image) - continue; - QByteArray imageByteArray; - QBuffer pngBuffer(&imageByteArray); - pngBuffer.open(QIODevice::WriteOnly); - image->save(&pngBuffer, "PNG"); - if (imageByteArray.size() > 0) - ds3Writer.add("images/" + imageId.toString() + ".png", "asset", &imageByteArray); - } - } - } - - if (ds3Writer.save(filename)) { - setCurrentFilename(filename); - } - - QApplication::restoreOverrideCursor(); -} - -void SkeletonDocumentWindow::open() -{ - if (!m_documentSaved) { - QMessageBox::StandardButton answer = QMessageBox::question(this, - APP_NAME, - tr("Do you really want to open another file and lose the unsaved changes?"), - QMessageBox::Yes | QMessageBox::No); - if (answer != QMessageBox::Yes) - return; - } - - QString filename = QFileDialog::getOpenFileName(this, QString(), QString(), - tr("Dust3D Document (*.ds3)")); - if (filename.isEmpty()) - return; - - QApplication::setOverrideCursor(Qt::WaitCursor); - Ds3FileReader ds3Reader(filename); - - for (int i = 0; i < ds3Reader.items().size(); ++i) { - Ds3ReaderItem item = ds3Reader.items().at(i); - if (item.type == "asset") { - if (item.name.startsWith("images/")) { - QString filename = item.name.split("/")[1]; - QString imageIdString = filename.split(".")[0]; - QUuid imageId = QUuid(imageIdString); - if (!imageId.isNull()) { - QByteArray data; - ds3Reader.loadItem(item.name, &data); - QImage image = QImage::fromData(data, "PNG"); - (void)ImageForever::add(&image, imageId); - } - } - } - } - - for (int i = 0; i < ds3Reader.items().size(); ++i) { - Ds3ReaderItem item = ds3Reader.items().at(i); - if (item.type == "model") { - QByteArray data; - ds3Reader.loadItem(item.name, &data); - QXmlStreamReader stream(data); - Snapshot snapshot; - loadSkeletonFromXmlStream(&snapshot, stream); - m_document->fromSnapshot(snapshot); - m_document->saveSnapshot(); - } else if (item.type == "asset") { - if (item.name == "canvas.png") { - QByteArray data; - ds3Reader.loadItem(item.name, &data); - QImage image = QImage::fromData(data, "PNG"); - m_document->updateTurnaround(image); - } - } - } - QApplication::restoreOverrideCursor(); - - setCurrentFilename(filename); -} - -void SkeletonDocumentWindow::showAdvanceSetting() -{ - if (nullptr == m_advanceSettingWidget) { - m_advanceSettingWidget = new AdvanceSettingWidget(m_document, this); - } - m_advanceSettingWidget->show(); - m_advanceSettingWidget->raise(); -} - -void SkeletonDocumentWindow::exportObjResult() -{ - QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), - tr("Wavefront (*.obj)")); - if (filename.isEmpty()) { - return; - } - QApplication::setOverrideCursor(Qt::WaitCursor); - MeshLoader *resultMesh = m_document->takeResultMesh(); - if (nullptr != resultMesh) { - resultMesh->exportAsObj(filename); - delete resultMesh; - } - QApplication::restoreOverrideCursor(); -} - -void SkeletonDocumentWindow::showExportPreview() -{ - if (nullptr == m_exportPreviewWidget) { - m_exportPreviewWidget = new ExportPreviewWidget(m_document, this); - connect(m_exportPreviewWidget, &ExportPreviewWidget::regenerate, m_document, &Document::regenerateMesh); - connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsGltf, this, &SkeletonDocumentWindow::exportGltfResult); - connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsFbx, this, &SkeletonDocumentWindow::exportFbxResult); - connect(m_document, &Document::resultMeshChanged, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner); - connect(m_document, &Document::exportReady, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner); - connect(m_document, &Document::resultTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview); - connect(m_document, &Document::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview); - registerDialog(m_exportPreviewWidget); - } - m_exportPreviewWidget->show(); - m_exportPreviewWidget->raise(); -} - -void SkeletonDocumentWindow::exportFbxResult() -{ - QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), - tr("Autodesk FBX (.fbx)")); - if (filename.isEmpty()) { - return; - } - if (!m_document->isExportReady()) { - qDebug() << "Export but document is not export ready"; - return; - } - QApplication::setOverrideCursor(Qt::WaitCursor); - Outcome skeletonResult = m_document->currentPostProcessedResultContext(); - FbxFileWriter fbxFileWriter(skeletonResult, m_document->resultRigBones(), m_document->resultRigWeights(), filename); - fbxFileWriter.save(); - QApplication::restoreOverrideCursor(); -} - -void SkeletonDocumentWindow::exportGltfResult() -{ - QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), - tr("GL Transmission Format (.gltf)")); - if (filename.isEmpty()) { - return; - } - if (!m_document->isExportReady()) { - qDebug() << "Export but document is not export ready"; - return; - } - QApplication::setOverrideCursor(Qt::WaitCursor); - Outcome skeletonResult = m_document->currentPostProcessedResultContext(); - GltfFileWriter gltfFileWriter(skeletonResult, m_document->resultRigBones(), m_document->resultRigWeights(), filename); - gltfFileWriter.save(); - if (m_document->textureImage) - m_document->textureImage->save(gltfFileWriter.textureFilenameInGltf()); - QFileInfo nameInfo(filename); - QString borderFilename = nameInfo.path() + QDir::separator() + nameInfo.completeBaseName() + "-BORDER.png"; - if (m_document->textureBorderImage) - m_document->textureBorderImage->save(borderFilename); - QString ambientOcclusionFilename = nameInfo.path() + QDir::separator() + nameInfo.completeBaseName() + "-AMBIENT-OCCLUSION.png"; - if (m_document->textureAmbientOcclusionImage) - m_document->textureAmbientOcclusionImage->save(ambientOcclusionFilename); - QString colorFilename = nameInfo.path() + QDir::separator() + nameInfo.completeBaseName() + "-COLOR.png"; - if (m_document->textureColorImage) - m_document->textureColorImage->save(colorFilename); - QApplication::restoreOverrideCursor(); -} - -void SkeletonDocumentWindow::updateXlockButtonState() -{ - if (m_document->xlocked) - m_xlockButton->setStyleSheet("QPushButton {color: #252525}"); - else - m_xlockButton->setStyleSheet("QPushButton {color: #fc6621}"); -} - -void SkeletonDocumentWindow::updateYlockButtonState() -{ - if (m_document->ylocked) - m_ylockButton->setStyleSheet("QPushButton {color: #252525}"); - else - m_ylockButton->setStyleSheet("QPushButton {color: #2a5aac}"); -} - -void SkeletonDocumentWindow::updateZlockButtonState() -{ - if (m_document->zlocked) - m_zlockButton->setStyleSheet("QPushButton {color: #252525}"); - else - m_zlockButton->setStyleSheet("QPushButton {color: #aaebc4}"); -} - -void SkeletonDocumentWindow::updateRadiusLockButtonState() -{ - if (m_document->radiusLocked) - m_radiusLockButton->setStyleSheet("QPushButton {color: #252525}"); - else - m_radiusLockButton->setStyleSheet("QPushButton {color: " + Theme::white.name() + "}"); -} - -void SkeletonDocumentWindow::updateRigWeightRenderWidget() -{ - MeshLoader *resultRigWeightMesh = m_document->takeResultRigWeightMesh(); - if (nullptr == resultRigWeightMesh) { - m_rigWidget->rigWeightRenderWidget()->hide(); - } else { - m_rigWidget->rigWeightRenderWidget()->updateMesh(resultRigWeightMesh); - m_rigWidget->rigWeightRenderWidget()->show(); - m_rigWidget->rigWeightRenderWidget()->update(); - } -} - -void SkeletonDocumentWindow::registerDialog(QWidget *widget) -{ - m_dialogs.push_back(widget); -} - -void SkeletonDocumentWindow::unregisterDialog(QWidget *widget) -{ - m_dialogs.erase(std::remove(m_dialogs.begin(), m_dialogs.end(), widget), m_dialogs.end()); -} diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index f246e703..22e62ced 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -37,15 +37,15 @@ #include "fbxfile.h" #include "shortcuts.h" -int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16; -int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16; -int SkeletonDocumentWindow::m_modelRenderWidgetInitialSize = 128; -int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialX = SkeletonDocumentWindow::m_modelRenderWidgetInitialX + SkeletonDocumentWindow::m_modelRenderWidgetInitialSize + 16; -int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialY = SkeletonDocumentWindow::m_modelRenderWidgetInitialY; -int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialSize = SkeletonDocumentWindow::m_modelRenderWidgetInitialSize; +int DocumentWindow::m_modelRenderWidgetInitialX = 16; +int DocumentWindow::m_modelRenderWidgetInitialY = 16; +int DocumentWindow::m_modelRenderWidgetInitialSize = 128; +int DocumentWindow::m_skeletonRenderWidgetInitialX = DocumentWindow::m_modelRenderWidgetInitialX + DocumentWindow::m_modelRenderWidgetInitialSize + 16; +int DocumentWindow::m_skeletonRenderWidgetInitialY = DocumentWindow::m_modelRenderWidgetInitialY; +int DocumentWindow::m_skeletonRenderWidgetInitialSize = DocumentWindow::m_modelRenderWidgetInitialSize; LogBrowser *g_logBrowser = nullptr; -std::set g_documentWindows; +std::set g_documentWindows; QTextBrowser *g_acknowlegementsWidget = nullptr; AboutWidget *g_aboutWidget = nullptr; QTextBrowser *g_contributorsWidget = nullptr; @@ -56,7 +56,7 @@ void outputMessage(QtMsgType type, const QMessageLogContext &context, const QStr g_logBrowser->outputMessage(type, msg, context.file, context.line); } -void SkeletonDocumentWindow::showAcknowlegements() +void DocumentWindow::showAcknowlegements() { if (!g_acknowlegementsWidget) { g_acknowlegementsWidget = new QTextBrowser; @@ -72,7 +72,7 @@ void SkeletonDocumentWindow::showAcknowlegements() g_acknowlegementsWidget->raise(); } -void SkeletonDocumentWindow::showContributors() +void DocumentWindow::showContributors() { if (!g_contributorsWidget) { g_contributorsWidget = new QTextBrowser; @@ -89,7 +89,7 @@ void SkeletonDocumentWindow::showContributors() g_contributorsWidget->raise(); } -void SkeletonDocumentWindow::showAbout() +void DocumentWindow::showAbout() { if (!g_aboutWidget) { g_aboutWidget = new AboutWidget; @@ -99,7 +99,7 @@ void SkeletonDocumentWindow::showAbout() g_aboutWidget->raise(); } -SkeletonDocumentWindow::SkeletonDocumentWindow() : +DocumentWindow::DocumentWindow() : m_document(nullptr), m_firstShow(true), m_documentSaved(true), @@ -220,9 +220,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_modelRenderWidget = new ModelWidget(containerWidget); m_modelRenderWidget->setAttribute(Qt::WA_TransparentForMouseEvents); - m_modelRenderWidget->setMinimumSize(SkeletonDocumentWindow::m_modelRenderWidgetInitialSize, SkeletonDocumentWindow::m_modelRenderWidgetInitialSize); + m_modelRenderWidget->setMinimumSize(DocumentWindow::m_modelRenderWidgetInitialSize, DocumentWindow::m_modelRenderWidgetInitialSize); m_modelRenderWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - m_modelRenderWidget->move(SkeletonDocumentWindow::m_modelRenderWidgetInitialX, SkeletonDocumentWindow::m_modelRenderWidgetInitialY); + m_modelRenderWidget->move(DocumentWindow::m_modelRenderWidgetInitialX, DocumentWindow::m_modelRenderWidgetInitialY); m_graphicsWidget->setModelWidget(m_modelRenderWidget); containerWidget->setModelWidget(m_modelRenderWidget); @@ -246,8 +246,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : materialDocker->setAllowedAreas(Qt::RightDockWidgetArea); MaterialManageWidget *materialManageWidget = new MaterialManageWidget(m_document, materialDocker); materialDocker->setWidget(materialManageWidget); - connect(materialManageWidget, &MaterialManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog); - connect(materialManageWidget, &MaterialManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog); + connect(materialManageWidget, &MaterialManageWidget::registerDialog, this, &DocumentWindow::registerDialog); + connect(materialManageWidget, &MaterialManageWidget::unregisterDialog, this, &DocumentWindow::unregisterDialog); addDockWidget(Qt::RightDockWidgetArea, materialDocker); connect(materialDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) { Q_UNUSED(topLevel); @@ -269,8 +269,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : poseDocker->setAllowedAreas(Qt::RightDockWidgetArea); PoseManageWidget *poseManageWidget = new PoseManageWidget(m_document, poseDocker); poseDocker->setWidget(poseManageWidget); - connect(poseManageWidget, &PoseManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog); - connect(poseManageWidget, &PoseManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog); + connect(poseManageWidget, &PoseManageWidget::registerDialog, this, &DocumentWindow::registerDialog); + connect(poseManageWidget, &PoseManageWidget::unregisterDialog, this, &DocumentWindow::unregisterDialog); addDockWidget(Qt::RightDockWidgetArea, poseDocker); connect(poseDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) { Q_UNUSED(topLevel); @@ -282,8 +282,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : motionDocker->setAllowedAreas(Qt::RightDockWidgetArea); MotionManageWidget *motionManageWidget = new MotionManageWidget(m_document, motionDocker); motionDocker->setWidget(motionManageWidget); - connect(motionManageWidget, &MotionManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog); - connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog); + connect(motionManageWidget, &MotionManageWidget::registerDialog, this, &DocumentWindow::registerDialog); + connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &DocumentWindow::unregisterDialog); addDockWidget(Qt::RightDockWidgetArea, motionDocker); tabifyDockWidget(partTreeDocker, materialDocker); @@ -309,27 +309,27 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_fileMenu = menuBar()->addMenu(tr("File")); m_newWindowAction = new QAction(tr("New Window"), this); - connect(m_newWindowAction, &QAction::triggered, this, &SkeletonDocumentWindow::newWindow, Qt::QueuedConnection); + connect(m_newWindowAction, &QAction::triggered, this, &DocumentWindow::newWindow, Qt::QueuedConnection); m_fileMenu->addAction(m_newWindowAction); m_newDocumentAction = new QAction(tr("New"), this); - connect(m_newDocumentAction, &QAction::triggered, this, &SkeletonDocumentWindow::newDocument); + connect(m_newDocumentAction, &QAction::triggered, this, &DocumentWindow::newDocument); m_fileMenu->addAction(m_newDocumentAction); m_openAction = new QAction(tr("Open..."), this); - connect(m_openAction, &QAction::triggered, this, &SkeletonDocumentWindow::open, Qt::QueuedConnection); + connect(m_openAction, &QAction::triggered, this, &DocumentWindow::open, Qt::QueuedConnection); m_fileMenu->addAction(m_openAction); m_saveAction = new QAction(tr("Save"), this); - connect(m_saveAction, &QAction::triggered, this, &SkeletonDocumentWindow::save, Qt::QueuedConnection); + connect(m_saveAction, &QAction::triggered, this, &DocumentWindow::save, Qt::QueuedConnection); m_fileMenu->addAction(m_saveAction); m_saveAsAction = new QAction(tr("Save As..."), this); - connect(m_saveAsAction, &QAction::triggered, this, &SkeletonDocumentWindow::saveAs, Qt::QueuedConnection); + connect(m_saveAsAction, &QAction::triggered, this, &DocumentWindow::saveAs, Qt::QueuedConnection); m_fileMenu->addAction(m_saveAsAction); m_saveAllAction = new QAction(tr("Save All"), this); - connect(m_saveAllAction, &QAction::triggered, this, &SkeletonDocumentWindow::saveAll, Qt::QueuedConnection); + connect(m_saveAllAction, &QAction::triggered, this, &DocumentWindow::saveAll, Qt::QueuedConnection); m_fileMenu->addAction(m_saveAllAction); m_fileMenu->addSeparator(); @@ -337,11 +337,11 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : //m_exportMenu = m_fileMenu->addMenu(tr("Export")); m_exportAction = new QAction(tr("Export..."), this); - connect(m_exportAction, &QAction::triggered, this, &SkeletonDocumentWindow::showExportPreview, Qt::QueuedConnection); + connect(m_exportAction, &QAction::triggered, this, &DocumentWindow::showExportPreview, Qt::QueuedConnection); m_fileMenu->addAction(m_exportAction); m_exportAsObjAction = new QAction(tr("Export as OBJ..."), this); - connect(m_exportAsObjAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportObjResult, Qt::QueuedConnection); + connect(m_exportAsObjAction, &QAction::triggered, this, &DocumentWindow::exportObjResult, Qt::QueuedConnection); m_fileMenu->addAction(m_exportAsObjAction); //m_exportRenderedAsImageAction = new QAction(tr("Export as PNG..."), this); @@ -355,7 +355,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_fileMenu->addSeparator(); m_changeTurnaroundAction = new QAction(tr("Change Reference Sheet..."), this); - connect(m_changeTurnaroundAction, &QAction::triggered, this, &SkeletonDocumentWindow::changeTurnaround, Qt::QueuedConnection); + connect(m_changeTurnaroundAction, &QAction::triggered, this, &DocumentWindow::changeTurnaround, Qt::QueuedConnection); m_fileMenu->addAction(m_changeTurnaroundAction); m_fileMenu->addSeparator(); @@ -530,7 +530,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_resetModelWidgetPosAction = new QAction(tr("Show Model"), this); connect(m_resetModelWidgetPosAction, &QAction::triggered, [=]() { if (!isModelSitInVisibleArea(m_modelRenderWidget)) { - m_modelRenderWidget->move(SkeletonDocumentWindow::m_modelRenderWidgetInitialX, SkeletonDocumentWindow::m_modelRenderWidgetInitialY); + m_modelRenderWidget->move(DocumentWindow::m_modelRenderWidgetInitialX, DocumentWindow::m_modelRenderWidgetInitialY); } }); m_viewMenu->addAction(m_resetModelWidgetPosAction); @@ -610,7 +610,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_windowMenu->addAction(m_showDebugDialogAction); m_showAdvanceSettingAction = new QAction(tr("Advance"), this); - connect(m_showAdvanceSettingAction, &QAction::triggered, this, &SkeletonDocumentWindow::showAdvanceSetting); + connect(m_showAdvanceSettingAction, &QAction::triggered, this, &DocumentWindow::showAdvanceSetting); #ifndef NDEBUG m_windowMenu->addAction(m_showAdvanceSettingAction); #endif @@ -618,33 +618,33 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_helpMenu = menuBar()->addMenu(tr("Help")); m_viewSourceAction = new QAction(tr("Fork me on GitHub"), this); - connect(m_viewSourceAction, &QAction::triggered, this, &SkeletonDocumentWindow::viewSource); + connect(m_viewSourceAction, &QAction::triggered, this, &DocumentWindow::viewSource); m_helpMenu->addAction(m_viewSourceAction); m_helpMenu->addSeparator(); m_seeReferenceGuideAction = new QAction(tr("Reference Guide"), this); - connect(m_seeReferenceGuideAction, &QAction::triggered, this, &SkeletonDocumentWindow::seeReferenceGuide); + connect(m_seeReferenceGuideAction, &QAction::triggered, this, &DocumentWindow::seeReferenceGuide); m_helpMenu->addAction(m_seeReferenceGuideAction); m_helpMenu->addSeparator(); m_aboutAction = new QAction(tr("About"), this); - connect(m_aboutAction, &QAction::triggered, this, &SkeletonDocumentWindow::about); + connect(m_aboutAction, &QAction::triggered, this, &DocumentWindow::about); m_helpMenu->addAction(m_aboutAction); m_reportIssuesAction = new QAction(tr("Report Issues"), this); - connect(m_reportIssuesAction, &QAction::triggered, this, &SkeletonDocumentWindow::reportIssues); + connect(m_reportIssuesAction, &QAction::triggered, this, &DocumentWindow::reportIssues); m_helpMenu->addAction(m_reportIssuesAction); m_helpMenu->addSeparator(); m_seeContributorsAction = new QAction(tr("Contributors"), this); - connect(m_seeContributorsAction, &QAction::triggered, this, &SkeletonDocumentWindow::seeContributors); + connect(m_seeContributorsAction, &QAction::triggered, this, &DocumentWindow::seeContributors); m_helpMenu->addAction(m_seeContributorsAction); m_seeAcknowlegementsAction = new QAction(tr("Acknowlegements"), this); - connect(m_seeAcknowlegementsAction, &QAction::triggered, this, &SkeletonDocumentWindow::seeAcknowlegements); + connect(m_seeAcknowlegementsAction, &QAction::triggered, this, &DocumentWindow::seeAcknowlegements); m_helpMenu->addAction(m_seeAcknowlegementsAction); connect(containerWidget, &GraphicsContainerWidget::containerSizeChanged, @@ -737,9 +737,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(graphicsWidget, &SkeletonGraphicsWidget::enableAllPositionRelatedLocks, m_document, &Document::enableAllPositionRelatedLocks); connect(graphicsWidget, &SkeletonGraphicsWidget::disableAllPositionRelatedLocks, m_document, &Document::disableAllPositionRelatedLocks); - connect(graphicsWidget, &SkeletonGraphicsWidget::changeTurnaround, this, &SkeletonDocumentWindow::changeTurnaround); - connect(graphicsWidget, &SkeletonGraphicsWidget::save, this, &SkeletonDocumentWindow::save); - connect(graphicsWidget, &SkeletonGraphicsWidget::open, this, &SkeletonDocumentWindow::open); + connect(graphicsWidget, &SkeletonGraphicsWidget::changeTurnaround, this, &DocumentWindow::changeTurnaround); + connect(graphicsWidget, &SkeletonGraphicsWidget::save, this, &DocumentWindow::save); + connect(graphicsWidget, &SkeletonGraphicsWidget::open, this, &DocumentWindow::open); connect(m_document, &Document::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded); connect(m_document, &Document::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved); @@ -848,25 +848,25 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : //m_skeletonRenderWidget->setCursor(graphicsWidget->cursor()); }); - connect(m_document, &Document::skeletonChanged, this, &SkeletonDocumentWindow::documentChanged); - connect(m_document, &Document::turnaroundChanged, this, &SkeletonDocumentWindow::documentChanged); - connect(m_document, &Document::optionsChanged, this, &SkeletonDocumentWindow::documentChanged); - connect(m_document, &Document::rigChanged, this, &SkeletonDocumentWindow::documentChanged); + connect(m_document, &Document::skeletonChanged, this, &DocumentWindow::documentChanged); + connect(m_document, &Document::turnaroundChanged, this, &DocumentWindow::documentChanged); + connect(m_document, &Document::optionsChanged, this, &DocumentWindow::documentChanged); + connect(m_document, &Document::rigChanged, this, &DocumentWindow::documentChanged); connect(m_modelRenderWidget, &ModelWidget::customContextMenuRequested, [=](const QPoint &pos) { graphicsWidget->showContextMenu(graphicsWidget->mapFromGlobal(m_modelRenderWidget->mapToGlobal(pos))); }); - connect(m_document, &Document::xlockStateChanged, this, &SkeletonDocumentWindow::updateXlockButtonState); - connect(m_document, &Document::ylockStateChanged, this, &SkeletonDocumentWindow::updateYlockButtonState); - connect(m_document, &Document::zlockStateChanged, this, &SkeletonDocumentWindow::updateZlockButtonState); - connect(m_document, &Document::radiusLockStateChanged, this, &SkeletonDocumentWindow::updateRadiusLockButtonState); + connect(m_document, &Document::xlockStateChanged, this, &DocumentWindow::updateXlockButtonState); + connect(m_document, &Document::ylockStateChanged, this, &DocumentWindow::updateYlockButtonState); + connect(m_document, &Document::zlockStateChanged, this, &DocumentWindow::updateZlockButtonState); + connect(m_document, &Document::radiusLockStateChanged, this, &DocumentWindow::updateRadiusLockButtonState); connect(m_rigWidget, &RigWidget::setRigType, m_document, &Document::setRigType); connect(m_document, &Document::rigTypeChanged, m_rigWidget, &RigWidget::rigTypeChanged); connect(m_document, &Document::resultRigChanged, m_rigWidget, &RigWidget::updateResultInfo); - connect(m_document, &Document::resultRigChanged, this, &SkeletonDocumentWindow::updateRigWeightRenderWidget); + connect(m_document, &Document::resultRigChanged, this, &DocumentWindow::updateRigWeightRenderWidget); //connect(m_document, &SkeletonDocument::resultRigChanged, tetrapodPoseEditWidget, &TetrapodPoseEditWidget::updatePreview); @@ -893,7 +893,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : initShortCuts(this, m_graphicsWidget); - connect(this, &SkeletonDocumentWindow::initialized, m_document, &Document::uiReady); + connect(this, &DocumentWindow::initialized, m_document, &Document::uiReady); QTimer *timer = new QTimer(this); timer->setInterval(250); @@ -906,15 +906,15 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : timer->start(); } -SkeletonDocumentWindow *SkeletonDocumentWindow::createDocumentWindow() +DocumentWindow *DocumentWindow::createDocumentWindow() { - SkeletonDocumentWindow *documentWindow = new SkeletonDocumentWindow(); + DocumentWindow *documentWindow = new DocumentWindow(); documentWindow->setAttribute(Qt::WA_DeleteOnClose); documentWindow->showMaximized(); return documentWindow; } -void SkeletonDocumentWindow::closeEvent(QCloseEvent *event) +void DocumentWindow::closeEvent(QCloseEvent *event) { if (m_documentSaved) { event->accept(); @@ -931,21 +931,21 @@ void SkeletonDocumentWindow::closeEvent(QCloseEvent *event) event->ignore(); } -void SkeletonDocumentWindow::setCurrentFilename(const QString &filename) +void DocumentWindow::setCurrentFilename(const QString &filename) { m_currentFilename = filename; m_documentSaved = true; updateTitle(); } -void SkeletonDocumentWindow::updateTitle() +void DocumentWindow::updateTitle() { QString appName = APP_NAME; QString appVer = APP_HUMAN_VER; setWindowTitle(QString("%1 %2 %3%4").arg(appName).arg(appVer).arg(m_currentFilename).arg(m_documentSaved ? "" : "*")); } -void SkeletonDocumentWindow::documentChanged() +void DocumentWindow::documentChanged() { if (m_documentSaved) { m_documentSaved = false; @@ -953,12 +953,12 @@ void SkeletonDocumentWindow::documentChanged() } } -void SkeletonDocumentWindow::newWindow() +void DocumentWindow::newWindow() { - SkeletonDocumentWindow::createDocumentWindow(); + DocumentWindow::createDocumentWindow(); } -void SkeletonDocumentWindow::newDocument() +void DocumentWindow::newDocument() { if (!m_documentSaved) { QMessageBox::StandardButton answer = QMessageBox::question(this, @@ -973,7 +973,7 @@ void SkeletonDocumentWindow::newDocument() m_document->saveSnapshot(); } -void SkeletonDocumentWindow::saveAs() +void DocumentWindow::saveAs() { QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), tr("Dust3D Document (*.ds3)")); @@ -983,50 +983,50 @@ void SkeletonDocumentWindow::saveAs() saveTo(filename); } -void SkeletonDocumentWindow::saveAll() +void DocumentWindow::saveAll() { for (auto &window: g_documentWindows) { window->save(); } } -void SkeletonDocumentWindow::viewSource() +void DocumentWindow::viewSource() { QString url = APP_REPOSITORY_URL; qDebug() << "viewSource:" << url; QDesktopServices::openUrl(QUrl(url)); } -void SkeletonDocumentWindow::about() +void DocumentWindow::about() { - SkeletonDocumentWindow::showAbout(); + DocumentWindow::showAbout(); } -void SkeletonDocumentWindow::reportIssues() +void DocumentWindow::reportIssues() { QString url = APP_ISSUES_URL; qDebug() << "reportIssues:" << url; QDesktopServices::openUrl(QUrl(url)); } -void SkeletonDocumentWindow::seeReferenceGuide() +void DocumentWindow::seeReferenceGuide() { QString url = APP_REFERENCE_GUIDE_URL; qDebug() << "referenceGuide:" << url; QDesktopServices::openUrl(QUrl(url)); } -void SkeletonDocumentWindow::seeAcknowlegements() +void DocumentWindow::seeAcknowlegements() { - SkeletonDocumentWindow::showAcknowlegements(); + DocumentWindow::showAcknowlegements(); } -void SkeletonDocumentWindow::seeContributors() +void DocumentWindow::seeContributors() { - SkeletonDocumentWindow::showContributors(); + DocumentWindow::showContributors(); } -void SkeletonDocumentWindow::initLockButton(QPushButton *button) +void DocumentWindow::initLockButton(QPushButton *button) { QFont font; font.setWeight(QFont::Light); @@ -1039,12 +1039,12 @@ void SkeletonDocumentWindow::initLockButton(QPushButton *button) button->setFocusPolicy(Qt::NoFocus); } -SkeletonDocumentWindow::~SkeletonDocumentWindow() +DocumentWindow::~DocumentWindow() { g_documentWindows.erase(this); } -void SkeletonDocumentWindow::showEvent(QShowEvent *event) +void DocumentWindow::showEvent(QShowEvent *event) { QMainWindow::showEvent(event); if (m_firstShow) { @@ -1056,12 +1056,12 @@ void SkeletonDocumentWindow::showEvent(QShowEvent *event) } } -void SkeletonDocumentWindow::mousePressEvent(QMouseEvent *event) +void DocumentWindow::mousePressEvent(QMouseEvent *event) { QMainWindow::mousePressEvent(event); } -void SkeletonDocumentWindow::changeTurnaround() +void DocumentWindow::changeTurnaround() { QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(), tr("Image Files (*.png *.jpg *.bmp)")).trimmed(); @@ -1073,12 +1073,12 @@ void SkeletonDocumentWindow::changeTurnaround() m_document->updateTurnaround(image); } -void SkeletonDocumentWindow::save() +void DocumentWindow::save() { saveTo(m_currentFilename); } -void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename) +void DocumentWindow::saveTo(const QString &saveAsFilename) { QString filename = saveAsFilename; @@ -1111,6 +1111,7 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename) ds3Writer.add("canvas.png", "asset", &imageByteArray); } + std::set imageIds; for (auto &material: snapshot.materials) { for (auto &layer: material.second) { for (auto &mapItem: layer.second) { @@ -1118,18 +1119,30 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename) if (findImageIdString == mapItem.end()) continue; QUuid imageId = QUuid(findImageIdString->second); - const QImage *image = ImageForever::get(imageId); - if (nullptr == image) - continue; - QByteArray imageByteArray; - QBuffer pngBuffer(&imageByteArray); - pngBuffer.open(QIODevice::WriteOnly); - image->save(&pngBuffer, "PNG"); - if (imageByteArray.size() > 0) - ds3Writer.add("images/" + imageId.toString() + ".png", "asset", &imageByteArray); + imageIds.insert(imageId); } } } + + for (auto &pose: snapshot.poses) { + auto findCanvasImageId = pose.first.find("canvasImageId"); + if (findCanvasImageId != pose.first.end()) { + QUuid imageId = QUuid(findCanvasImageId->second); + imageIds.insert(imageId); + } + } + + for (const auto &imageId: imageIds) { + const QImage *image = ImageForever::get(imageId); + if (nullptr == image) + continue; + QByteArray imageByteArray; + QBuffer pngBuffer(&imageByteArray); + pngBuffer.open(QIODevice::WriteOnly); + image->save(&pngBuffer, "PNG"); + if (imageByteArray.size() > 0) + ds3Writer.add("images/" + imageId.toString() + ".png", "asset", &imageByteArray); + } if (ds3Writer.save(filename)) { setCurrentFilename(filename); @@ -1138,7 +1151,7 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename) QApplication::restoreOverrideCursor(); } -void SkeletonDocumentWindow::open() +void DocumentWindow::open() { if (!m_documentSaved) { QMessageBox::StandardButton answer = QMessageBox::question(this, @@ -1202,7 +1215,7 @@ void SkeletonDocumentWindow::open() setCurrentFilename(filename); } -void SkeletonDocumentWindow::showAdvanceSetting() +void DocumentWindow::showAdvanceSetting() { if (nullptr == m_advanceSettingWidget) { m_advanceSettingWidget = new AdvanceSettingWidget(m_document, this); @@ -1211,7 +1224,7 @@ void SkeletonDocumentWindow::showAdvanceSetting() m_advanceSettingWidget->raise(); } -void SkeletonDocumentWindow::exportObjResult() +void DocumentWindow::exportObjResult() { QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), tr("Wavefront (*.obj)")); @@ -1227,13 +1240,13 @@ void SkeletonDocumentWindow::exportObjResult() QApplication::restoreOverrideCursor(); } -void SkeletonDocumentWindow::showExportPreview() +void DocumentWindow::showExportPreview() { if (nullptr == m_exportPreviewWidget) { m_exportPreviewWidget = new ExportPreviewWidget(m_document, this); connect(m_exportPreviewWidget, &ExportPreviewWidget::regenerate, m_document, &Document::regenerateMesh); - connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsGlb, this, &SkeletonDocumentWindow::exportGlbResult); - connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsFbx, this, &SkeletonDocumentWindow::exportFbxResult); + connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsGlb, this, &DocumentWindow::exportGlbResult); + connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsFbx, this, &DocumentWindow::exportFbxResult); connect(m_document, &Document::resultMeshChanged, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner); connect(m_document, &Document::exportReady, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner); connect(m_document, &Document::resultTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview); @@ -1244,7 +1257,7 @@ void SkeletonDocumentWindow::showExportPreview() m_exportPreviewWidget->raise(); } -void SkeletonDocumentWindow::exportFbxResult() +void DocumentWindow::exportFbxResult() { QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), tr("Autodesk FBX (.fbx)")); @@ -1270,7 +1283,7 @@ void SkeletonDocumentWindow::exportFbxResult() QApplication::restoreOverrideCursor(); } -void SkeletonDocumentWindow::exportGlbResult() +void DocumentWindow::exportGlbResult() { QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), tr("glTF Binary Format (.glb)")); @@ -1296,7 +1309,7 @@ void SkeletonDocumentWindow::exportGlbResult() QApplication::restoreOverrideCursor(); } -void SkeletonDocumentWindow::updateXlockButtonState() +void DocumentWindow::updateXlockButtonState() { if (m_document->xlocked) m_xlockButton->setStyleSheet("QPushButton {color: #252525}"); @@ -1304,7 +1317,7 @@ void SkeletonDocumentWindow::updateXlockButtonState() m_xlockButton->setStyleSheet("QPushButton {color: #fc6621}"); } -void SkeletonDocumentWindow::updateYlockButtonState() +void DocumentWindow::updateYlockButtonState() { if (m_document->ylocked) m_ylockButton->setStyleSheet("QPushButton {color: #252525}"); @@ -1312,7 +1325,7 @@ void SkeletonDocumentWindow::updateYlockButtonState() m_ylockButton->setStyleSheet("QPushButton {color: #2a5aac}"); } -void SkeletonDocumentWindow::updateZlockButtonState() +void DocumentWindow::updateZlockButtonState() { if (m_document->zlocked) m_zlockButton->setStyleSheet("QPushButton {color: #252525}"); @@ -1320,7 +1333,7 @@ void SkeletonDocumentWindow::updateZlockButtonState() m_zlockButton->setStyleSheet("QPushButton {color: #aaebc4}"); } -void SkeletonDocumentWindow::updateRadiusLockButtonState() +void DocumentWindow::updateRadiusLockButtonState() { if (m_document->radiusLocked) m_radiusLockButton->setStyleSheet("QPushButton {color: #252525}"); @@ -1328,7 +1341,7 @@ void SkeletonDocumentWindow::updateRadiusLockButtonState() m_radiusLockButton->setStyleSheet("QPushButton {color: " + Theme::white.name() + "}"); } -void SkeletonDocumentWindow::updateRigWeightRenderWidget() +void DocumentWindow::updateRigWeightRenderWidget() { MeshLoader *resultRigWeightMesh = m_document->takeResultRigWeightMesh(); if (nullptr == resultRigWeightMesh) { @@ -1340,12 +1353,12 @@ void SkeletonDocumentWindow::updateRigWeightRenderWidget() } } -void SkeletonDocumentWindow::registerDialog(QWidget *widget) +void DocumentWindow::registerDialog(QWidget *widget) { m_dialogs.push_back(widget); } -void SkeletonDocumentWindow::unregisterDialog(QWidget *widget) +void DocumentWindow::unregisterDialog(QWidget *widget) { m_dialogs.erase(std::remove(m_dialogs.begin(), m_dialogs.end(), widget), m_dialogs.end()); } diff --git a/src/documentwindow.h b/src/documentwindow.h index b9310aae..8b0ddc42 100644 --- a/src/documentwindow.h +++ b/src/documentwindow.h @@ -17,15 +17,15 @@ class SkeletonGraphicsWidget; -class SkeletonDocumentWindow : public QMainWindow +class DocumentWindow : public QMainWindow { Q_OBJECT signals: void initialized(); public: - SkeletonDocumentWindow(); - ~SkeletonDocumentWindow(); - static SkeletonDocumentWindow *createDocumentWindow(); + DocumentWindow(); + ~DocumentWindow(); + static DocumentWindow *createDocumentWindow(); static void showAcknowlegements(); static void showContributors(); static void showAbout(); diff --git a/src/genericposer.cpp b/src/genericposer.cpp index 2942136f..7ed178c0 100644 --- a/src/genericposer.cpp +++ b/src/genericposer.cpp @@ -8,50 +8,7 @@ GenericPoser::GenericPoser(const std::vector &bones) : void GenericPoser::commit() { - for (const auto &item: parameters()) { - int boneIndex = findBoneIndex(item.first); - if (-1 == boneIndex) { - continue; - } - auto findPitchResult = item.second.find("pitch"); - auto findYawResult = item.second.find("yaw"); - auto findRollResult = item.second.find("roll"); - if (findPitchResult != item.second.end() || - findYawResult != item.second.end() || - findRollResult != item.second.end()) { - float yawAngle = valueOfKeyInMapOrEmpty(item.second, "yaw").toFloat(); - if (item.first.startsWith("Left")) { - yawAngle = -yawAngle; - } - QQuaternion rotation = eulerAnglesToQuaternion(valueOfKeyInMapOrEmpty(item.second, "pitch").toFloat(), - yawAngle, - valueOfKeyInMapOrEmpty(item.second, "roll").toFloat()); - m_jointNodeTree.updateRotation(boneIndex, rotation); - continue; - } - auto findIntersectionResult = item.second.find("intersection"); - if (findIntersectionResult != item.second.end()) { - float intersectionAngle = valueOfKeyInMapOrEmpty(item.second, "intersection").toFloat(); - const RiggerBone &bone = bones()[boneIndex]; - QVector3D axis = bone.baseNormal; - QQuaternion rotation = QQuaternion::fromAxisAndAngle(axis, intersectionAngle); - m_jointNodeTree.updateRotation(boneIndex, rotation); - continue; - } - auto findXResult = item.second.find("x"); - auto findYResult = item.second.find("y"); - auto findZResult = item.second.find("z"); - if (findXResult != item.second.end() || - findYResult != item.second.end() || - findZResult != item.second.end()) { - float x = valueOfKeyInMapOrEmpty(item.second, "x").toFloat(); - float y = valueOfKeyInMapOrEmpty(item.second, "y").toFloat(); - float z = valueOfKeyInMapOrEmpty(item.second, "z").toFloat(); - QVector3D translation = {x, y, z}; - m_jointNodeTree.addTranslation(boneIndex, translation); - continue; - } - } + // TODO: Poser::commit(); } diff --git a/src/genericrigger.cpp b/src/genericrigger.cpp index 4bff8e80..e1a54d27 100644 --- a/src/genericrigger.cpp +++ b/src/genericrigger.cpp @@ -302,7 +302,7 @@ bool GenericRigger::rig() m_resultBones.push_back(RiggerBone()); RiggerBone &bodyBone = m_resultBones.back(); bodyBone.index = m_resultBones.size() - 1; - bodyBone.name = "Body"; + bodyBone.name = Rigger::rootBoneName; bodyBone.headPosition = QVector3D(0, 0, 0); bodyBone.hasButton = true; bodyBone.button = {spineNodes.size(), 0}; @@ -378,8 +378,8 @@ bool GenericRigger::rig() //qDebug() << spineBone.name << "head:" << spineBone.headPosition << "tail:" << spineBone.tailPosition; if (1 == spineGenerateOrder) { - m_resultBones[boneIndexMap["Body"]].tailPosition = spineBone.headPosition; - m_resultBones[boneIndexMap["Body"]].children.push_back(spineBone.index); + m_resultBones[boneIndexMap[Rigger::rootBoneName]].tailPosition = spineBone.headPosition; + m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(spineBone.index); } else { m_resultBones[boneIndexMap["Spine" + QString::number(spineGenerateOrder - 1)]].tailPosition = spineBone.headPosition; m_resultBones[boneIndexMap["Spine" + QString::number(spineGenerateOrder - 1)]].children.push_back(spineBone.index); @@ -396,7 +396,7 @@ bool GenericRigger::rig() ribBone.headPosition = spineBoneHeadPosition; boneIndexMap[ribBone.name] = ribBone.index; if (1 == spineGenerateOrder) { - m_resultBones[boneIndexMap["Body"]].children.push_back(ribBone.index); + m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(ribBone.index); } else { m_resultBones[boneIndexMap["Spine" + QString::number(spineGenerateOrder)]].children.push_back(ribBone.index); } diff --git a/src/main.cpp b/src/main.cpp index a3172576..4dc275c7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,7 +46,7 @@ int main(int argc, char ** argv) font.setBold(false); QApplication::setFont(font); - SkeletonDocumentWindow::createDocumentWindow(); + DocumentWindow::createDocumentWindow(); return app.exec(); } diff --git a/src/motionmanagewidget.cpp b/src/motionmanagewidget.cpp index cff342ec..3627f411 100644 --- a/src/motionmanagewidget.cpp +++ b/src/motionmanagewidget.cpp @@ -22,14 +22,21 @@ MotionManageWidget::MotionManageWidget(const Document *document, QWidget *parent connect(m_motionListWidget, &MotionListWidget::modifyMotion, this, &MotionManageWidget::showMotionDialog); InfoLabel *infoLabel = new InfoLabel; - infoLabel->setText(tr("Missing Rig")); infoLabel->show(); auto refreshInfoLabel = [=]() { if (m_document->currentRigSucceed()) { - infoLabel->hide(); - addMotionButton->show(); + if (m_document->rigType == RigType::Tetrapod) { + infoLabel->setText(""); + infoLabel->hide(); + addMotionButton->show(); + } else { + infoLabel->setText(tr("Motion editor doesn't support this rig type yet: ") + RigTypeToDispName(m_document->rigType)); + infoLabel->show(); + addMotionButton->hide(); + } } else { + infoLabel->setText(tr("Missing Rig")); infoLabel->show(); addMotionButton->hide(); } diff --git a/src/posedocument.cpp b/src/posedocument.cpp new file mode 100644 index 00000000..f3bc2ee5 --- /dev/null +++ b/src/posedocument.cpp @@ -0,0 +1,401 @@ +#include +#include "posedocument.h" +#include "rigger.h" +#include "util.h" + +constexpr float PoseDocument::m_nodeRadius = 0.01; +constexpr float PoseDocument::m_groundPlaneHalfThickness = m_nodeRadius / 4; + +bool PoseDocument::hasPastableNodesInClipboard() const +{ + return false; +} + +bool PoseDocument::originSettled() const +{ + return false; +} + +bool PoseDocument::isNodeEditable(QUuid nodeId) const +{ + return true; +} + +bool PoseDocument::isEdgeEditable(QUuid edgeId) const +{ + return true; +} + +void PoseDocument::copyNodes(std::set nodeIdSet) const +{ + // TODO: +} + +void PoseDocument::saveHistoryItem() +{ + PoseHistoryItem item; + toParameters(item.parameters); + m_undoItems.push_back(item); +} + +bool PoseDocument::undoable() const +{ + return m_undoItems.size() >= 2; +} + +bool PoseDocument::redoable() const +{ + return !m_redoItems.empty(); +} + +void PoseDocument::undo() +{ + if (!undoable()) + return; + m_redoItems.push_back(m_undoItems.back()); + m_undoItems.pop_back(); + const auto &item = m_undoItems.back(); + fromParameters(&m_riggerBones, item.parameters); +} + +void PoseDocument::redo() +{ + if (m_redoItems.empty()) + return; + m_undoItems.push_back(m_redoItems.back()); + const auto &item = m_redoItems.back(); + fromParameters(&m_riggerBones, item.parameters); + m_redoItems.pop_back(); +} + +void PoseDocument::paste() +{ +} + +void PoseDocument::updateTurnaround(const QImage &image) +{ + turnaround = image; + emit turnaroundChanged(); +} + +void PoseDocument::reset() +{ + nodeMap.clear(); + edgeMap.clear(); + partMap.clear(); + m_boneNameToIdsMap.clear(); + m_bonesPartId = QUuid(); + m_groundPartId = QUuid(); + m_groundEdgeId = QUuid(); + emit cleanup(); + emit parametersChanged(); +} + +void PoseDocument::fromParameters(const std::vector *rigBones, + const std::map> ¶meters) +{ + if (nullptr == rigBones || rigBones->empty()) { + m_riggerBones.clear(); + return; + } + + if (&m_riggerBones != rigBones) + m_riggerBones = *rigBones; + + QVector3D rootTranslation; + std::vector bones = *rigBones; + for (auto &bone: bones) { + const auto findParameterResult = parameters.find(bone.name); + if (findParameterResult == parameters.end()) + continue; + const auto &map = findParameterResult->second; + { + auto findXResult = map.find("fromX"); + auto findYResult = map.find("fromY"); + auto findZResult = map.find("fromZ"); + if (findXResult != map.end() || + findYResult != map.end() || + findZResult != map.end()) { + bone.headPosition = { + valueOfKeyInMapOrEmpty(map, "fromX").toFloat(), + valueOfKeyInMapOrEmpty(map, "fromY").toFloat(), + valueOfKeyInMapOrEmpty(map, "fromZ").toFloat() + }; + } + } + { + auto findXResult = map.find("toX"); + auto findYResult = map.find("toY"); + auto findZResult = map.find("toZ"); + if (findXResult != map.end() || + findYResult != map.end() || + findZResult != map.end()) { + bone.tailPosition = { + valueOfKeyInMapOrEmpty(map, "toX").toFloat(), + valueOfKeyInMapOrEmpty(map, "toY").toFloat(), + valueOfKeyInMapOrEmpty(map, "toZ").toFloat() + }; + } + } + } + + const auto findRoot = parameters.find(Rigger::rootBoneName); + if (findRoot != parameters.end()) { + const auto &map = findRoot->second; + { + auto findXResult = map.find("translateX"); + auto findYResult = map.find("translateY"); + auto findZResult = map.find("translateZ"); + if (findXResult != map.end() || + findYResult != map.end() || + findZResult != map.end()) { + rootTranslation = { + valueOfKeyInMapOrEmpty(map, "translateX").toFloat(), + valueOfKeyInMapOrEmpty(map, "translateY").toFloat(), + valueOfKeyInMapOrEmpty(map, "translateZ").toFloat() + }; + } + } + } + + updateRigBones(&bones, rootTranslation); +} + +void PoseDocument::updateRigBones(const std::vector *rigBones, const QVector3D &rootTranslation) +{ + reset(); + + if (nullptr == rigBones || rigBones->empty()) { + return; + } + + std::set newAddedNodeIds; + std::set newAddedEdgeIds; + + m_bonesPartId = QUuid::createUuid(); + auto &bonesPart = partMap[m_bonesPartId]; + bonesPart.id = m_bonesPartId; + + std::vector> edgePairs; + for (size_t i = 1; i < rigBones->size(); ++i) { + const auto &bone = (*rigBones)[i]; + for (const auto &child: bone.children) { + edgePairs.push_back({i, child}); + } + } + std::map boneIndexToHeadNodeIdMap; + for (const auto &edgePair: edgePairs) { + QUuid firstNodeId, secondNodeId; + auto findFirst = boneIndexToHeadNodeIdMap.find(edgePair.first); + if (findFirst == boneIndexToHeadNodeIdMap.end()) { + const auto &bone = (*rigBones)[edgePair.first]; + SkeletonNode node; + node.partId = m_bonesPartId; + node.id = QUuid::createUuid(); + node.setRadius(m_nodeRadius); + node.x = bone.headPosition.x() + 0.5; + node.y = -bone.headPosition.y() + 0.5; + node.z = -bone.headPosition.z() + 1; + nodeMap[node.id] = node; + newAddedNodeIds.insert(node.id); + boneIndexToHeadNodeIdMap[edgePair.first] = node.id; + firstNodeId = node.id; + } else { + firstNodeId = findFirst->second; + } + auto findSecond = boneIndexToHeadNodeIdMap.find(edgePair.second); + if (findSecond == boneIndexToHeadNodeIdMap.end()) { + const auto &bone = (*rigBones)[edgePair.second]; + SkeletonNode node; + node.partId = m_bonesPartId; + node.id = QUuid::createUuid(); + node.setRadius(m_nodeRadius); + node.x = bone.headPosition.x() + 0.5; + node.y = -bone.headPosition.y() + 0.5; + node.z = -bone.headPosition.z() + 1; + nodeMap[node.id] = node; + newAddedNodeIds.insert(node.id); + boneIndexToHeadNodeIdMap[edgePair.second] = node.id; + secondNodeId = node.id; + } else { + secondNodeId = findSecond->second; + } + + SkeletonEdge edge; + edge.partId = m_bonesPartId; + edge.id = QUuid::createUuid(); + edge.nodeIds.push_back(firstNodeId); + edge.nodeIds.push_back(secondNodeId); + edgeMap[edge.id] = edge; + newAddedEdgeIds.insert(edge.id); + nodeMap[firstNodeId].edgeIds.push_back(edge.id); + nodeMap[secondNodeId].edgeIds.push_back(edge.id); + } + for (size_t i = 0; i < rigBones->size(); ++i) { + const auto &bone = (*rigBones)[i]; + if (bone.children.empty()) { + const QUuid &firstNodeId = boneIndexToHeadNodeIdMap[i]; + + SkeletonNode node; + node.partId = m_bonesPartId; + node.id = QUuid::createUuid(); + node.setRadius(m_nodeRadius / 2); + node.x = bone.tailPosition.x() + 0.5; + node.y = -bone.tailPosition.y() + 0.5; + node.z = -bone.tailPosition.z() + 1; + nodeMap[node.id] = node; + newAddedNodeIds.insert(node.id); + m_boneNameToIdsMap[bone.name] = {firstNodeId, node.id}; + + SkeletonEdge edge; + edge.partId = m_bonesPartId; + edge.id = QUuid::createUuid(); + edge.nodeIds.push_back(firstNodeId); + edge.nodeIds.push_back(node.id); + edgeMap[edge.id] = edge; + newAddedEdgeIds.insert(edge.id); + nodeMap[firstNodeId].edgeIds.push_back(edge.id); + nodeMap[node.id].edgeIds.push_back(edge.id); + continue; + } + for (const auto &child: bone.children) { + m_boneNameToIdsMap[bone.name] = {boneIndexToHeadNodeIdMap[i], boneIndexToHeadNodeIdMap[child]}; + } + } + + m_groundPartId = QUuid::createUuid(); + auto &groundPart = partMap[m_groundPartId]; + groundPart.id = m_groundPartId; + + float groundY = findGroundY() + rootTranslation.y(); + + std::pair groundNodesPair; + { + SkeletonNode node; + node.partId = m_groundPartId; + node.id = QUuid::createUuid(); + node.setRadius(m_groundPlaneHalfThickness); + node.x = -100; + node.y = groundY + m_groundPlaneHalfThickness; + node.z = -100; + nodeMap[node.id] = node; + newAddedNodeIds.insert(node.id); + groundNodesPair.first = node.id; + } + + { + SkeletonNode node; + node.partId = m_groundPartId; + node.id = QUuid::createUuid(); + node.setRadius(m_groundPlaneHalfThickness); + node.x = 100; + node.y = groundY + m_groundPlaneHalfThickness; + node.z = 100; + nodeMap[node.id] = node; + newAddedNodeIds.insert(node.id); + groundNodesPair.second = node.id; + } + + { + SkeletonEdge edge; + edge.partId = m_groundPartId; + edge.id = QUuid::createUuid(); + edge.nodeIds.push_back(groundNodesPair.first); + edge.nodeIds.push_back(groundNodesPair.second); + edgeMap[edge.id] = edge; + m_groundEdgeId = edge.id; + newAddedEdgeIds.insert(edge.id); + nodeMap[groundNodesPair.first].edgeIds.push_back(edge.id); + nodeMap[groundNodesPair.second].edgeIds.push_back(edge.id); + } + + for (const auto &nodeIt: newAddedNodeIds) { + emit nodeAdded(nodeIt); + } + for (const auto &edgeIt: newAddedEdgeIds) { + emit edgeAdded(edgeIt); + } + + emit parametersChanged(); +} + +void PoseDocument::moveNodeBy(QUuid nodeId, float x, float y, float z) +{ + auto it = nodeMap.find(nodeId); + if (it == nodeMap.end()) { + qDebug() << "Find node failed:" << nodeId; + return; + } + it->second.x += x; + it->second.y += y; + it->second.z += z; + emit nodeOriginChanged(it->first); + emit parametersChanged(); +} + +void PoseDocument::setNodeOrigin(QUuid nodeId, float x, float y, float z) +{ + auto it = nodeMap.find(nodeId); + if (it == nodeMap.end()) { + qDebug() << "Find node failed:" << nodeId; + return; + } + it->second.x = x; + it->second.y = y; + it->second.z = z; + auto part = partMap.find(it->second.partId); + if (part != partMap.end()) + part->second.dirty = true; + emit nodeOriginChanged(nodeId); + emit parametersChanged(); +} + +float PoseDocument::findGroundY() const +{ + auto maxY = std::numeric_limits::lowest(); + for (const auto &nodeIt: nodeMap) { + if (nodeIt.second.partId != m_bonesPartId) + continue; + auto y = nodeIt.second.y + nodeIt.second.radius; + if (y > maxY) + maxY = y; + } + return maxY; +} + +void PoseDocument::toParameters(std::map> ¶meters) const +{ + float translateY = 0; + auto findGroundEdge = edgeMap.find(m_groundEdgeId); + if (findGroundEdge != edgeMap.end()) { + const auto &nodeIds = findGroundEdge->second.nodeIds; + if (nodeIds.size() == 2) { + auto findFirstNode = nodeMap.find(nodeIds[0]); + auto findSecondNode = nodeMap.find(nodeIds[1]); + if (findFirstNode != nodeMap.end() && findSecondNode != nodeMap.end()) { + translateY = (findFirstNode->second.y + findSecondNode->second.y) / 2 - + (findGroundY() + m_groundPlaneHalfThickness); + } + } + } + if (!qFuzzyIsNull(translateY)) { + auto &boneParameter = parameters[Rigger::rootBoneName]; + boneParameter["translateY"] = QString::number(translateY); + } + for (const auto &item: m_boneNameToIdsMap) { + auto &boneParameter = parameters[item.first]; + const auto &boneNodeIdPair = item.second; + auto findFirstNode = nodeMap.find(boneNodeIdPair.first); + if (findFirstNode == nodeMap.end()) + continue; + auto findSecondNode = nodeMap.find(boneNodeIdPair.second); + if (findSecondNode == nodeMap.end()) + continue; + boneParameter["fromX"] = QString::number(findFirstNode->second.x - 0.5); + boneParameter["fromY"] = QString::number(0.5 - findFirstNode->second.y); + boneParameter["fromZ"] = QString::number(1.0 - findFirstNode->second.z); + boneParameter["toX"] = QString::number(findSecondNode->second.x - 0.5); + boneParameter["toY"] = QString::number(0.5 - findSecondNode->second.y); + boneParameter["toZ"] = QString::number(1.0 - findSecondNode->second.z); + } +} diff --git a/src/posedocument.h b/src/posedocument.h new file mode 100644 index 00000000..0910a96c --- /dev/null +++ b/src/posedocument.h @@ -0,0 +1,66 @@ +#ifndef DUST3D_POSE_DOCUMENT_H +#define DUST3D_POSE_DOCUMENT_H +#include +#include +#include +#include "skeletondocument.h" +#include "rigger.h" + +struct PoseHistoryItem +{ + std::map> parameters; +}; + +class PoseDocument : public SkeletonDocument +{ + Q_OBJECT +signals: + void turnaroundChanged(); + void cleanup(); + void nodeAdded(QUuid nodeId); + void edgeAdded(QUuid edgeId); + void nodeOriginChanged(QUuid nodeId); + void parametersChanged(); + +public: + bool undoable() const override; + bool redoable() const override; + bool hasPastableNodesInClipboard() const override; + bool originSettled() const override; + bool isNodeEditable(QUuid nodeId) const override; + bool isEdgeEditable(QUuid edgeId) const override; + void copyNodes(std::set nodeIdSet) const override; + + void updateTurnaround(const QImage &image); + void updateRigBones(const std::vector *rigBones, const QVector3D &rootTranslation=QVector3D(0, 0, 0)); + void reset(); + + void toParameters(std::map> ¶meters) const; + void fromParameters(const std::vector *rigBones, + const std::map> ¶meters); + +public slots: + void saveHistoryItem(); + void undo() override; + void redo() override; + void paste() override; + + void moveNodeBy(QUuid nodeId, float x, float y, float z); + void setNodeOrigin(QUuid nodeId, float x, float y, float z); + float findGroundY() const; + +public: + static const float m_nodeRadius; + static const float m_groundPlaneHalfThickness; + +private: + std::map> m_boneNameToIdsMap; + QUuid m_groundPartId; + QUuid m_bonesPartId; + QUuid m_groundEdgeId; + std::deque m_undoItems; + std::deque m_redoItems; + std::vector m_riggerBones; +}; + +#endif diff --git a/src/poseeditwidget.cpp b/src/poseeditwidget.cpp index 1b8ac0be..029389af 100644 --- a/src/poseeditwidget.cpp +++ b/src/poseeditwidget.cpp @@ -6,15 +6,21 @@ #include #include #include +#include #include "theme.h" #include "poseeditwidget.h" #include "floatnumberwidget.h" #include "version.h" #include "poserconstruct.h" +#include "graphicscontainerwidget.h" +#include "documentwindow.h" +#include "shortcuts.h" +#include "imageforever.h" PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : QDialog(parent), - m_document(document) + m_document(document), + m_poseDocument(new PoseDocument) { m_posePreviewManager = new PosePreviewManager(); connect(m_posePreviewManager, &PosePreviewManager::renderDone, [=]() { @@ -29,15 +35,53 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : m_previewWidget->updateMesh(m_posePreviewManager->takeResultPreviewMesh()); }); - m_previewWidget = new ModelWidget(this); - m_previewWidget->setMinimumSize(128, 128); - m_previewWidget->resize(384, 384); - m_previewWidget->move(-64, -64+22); + SkeletonGraphicsWidget *graphicsWidget = new SkeletonGraphicsWidget(m_poseDocument); + graphicsWidget->setNodePositionModifyOnly(true); + m_poseGraphicsWidget = graphicsWidget; + + initShortCuts(this, graphicsWidget); + + GraphicsContainerWidget *containerWidget = new GraphicsContainerWidget; + containerWidget->setGraphicsWidget(graphicsWidget); + QGridLayout *containerLayout = new QGridLayout; + containerLayout->setSpacing(0); + containerLayout->setContentsMargins(1, 0, 0, 0); + containerLayout->addWidget(graphicsWidget); + containerWidget->setLayout(containerLayout); + containerWidget->setMinimumSize(400, 400); + + m_previewWidget = new ModelWidget(containerWidget); + m_previewWidget->setAttribute(Qt::WA_TransparentForMouseEvents); + m_previewWidget->setMinimumSize(DocumentWindow::m_modelRenderWidgetInitialSize, DocumentWindow::m_modelRenderWidgetInitialSize); + m_previewWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + m_previewWidget->move(DocumentWindow::m_modelRenderWidgetInitialX, DocumentWindow::m_modelRenderWidgetInitialY); + + m_poseGraphicsWidget->setModelWidget(m_previewWidget); + containerWidget->setModelWidget(m_previewWidget); + + connect(containerWidget, &GraphicsContainerWidget::containerSizeChanged, + graphicsWidget, &SkeletonGraphicsWidget::canvasResized); + + connect(graphicsWidget, &SkeletonGraphicsWidget::moveNodeBy, m_poseDocument, &PoseDocument::moveNodeBy); + connect(graphicsWidget, &SkeletonGraphicsWidget::setNodeOrigin, m_poseDocument, &PoseDocument::setNodeOrigin); + connect(graphicsWidget, &SkeletonGraphicsWidget::groupOperationAdded, m_poseDocument, &PoseDocument::saveHistoryItem); + connect(graphicsWidget, &SkeletonGraphicsWidget::undo, m_poseDocument, &PoseDocument::undo); + connect(graphicsWidget, &SkeletonGraphicsWidget::redo, m_poseDocument, &PoseDocument::redo); + + connect(m_poseDocument, &PoseDocument::cleanup, graphicsWidget, &SkeletonGraphicsWidget::removeAllContent); + + connect(m_poseDocument, &PoseDocument::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded); + connect(m_poseDocument, &PoseDocument::edgeAdded, graphicsWidget, &SkeletonGraphicsWidget::edgeAdded); + connect(m_poseDocument, &PoseDocument::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged); + + connect(m_poseDocument, &PoseDocument::parametersChanged, this, [&]() { + m_parameters.clear(); + m_poseDocument->toParameters(m_parameters); + emit parametersAdjusted(); + }); QHBoxLayout *paramtersLayout = new QHBoxLayout; - paramtersLayout->setContentsMargins(0, 480, 0, 0); - paramtersLayout->addStretch(); - paramtersLayout->addSpacing(20); + paramtersLayout->addWidget(containerWidget); m_nameEdit = new QLineEdit; m_nameEdit->setFixedWidth(200); @@ -49,9 +93,17 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : connect(saveButton, &QPushButton::clicked, this, &PoseEditWidget::save); saveButton->setDefault(true); + QPushButton *changeReferenceSheet = new QPushButton(tr("Change Reference Sheet...")); + connect(changeReferenceSheet, &QPushButton::clicked, this, &PoseEditWidget::changeTurnaround); + connect(m_poseDocument, &PoseDocument::turnaroundChanged, + graphicsWidget, &SkeletonGraphicsWidget::turnaroundChanged); + + 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->addStretch(); baseInfoLayout->addWidget(saveButton); @@ -62,7 +114,7 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : setLayout(mainLayout); - connect(m_document, &Document::resultRigChanged, this, &PoseEditWidget::updatePreview); + connect(m_document, &Document::resultRigChanged, this, &PoseEditWidget::updatePoseDocument); connect(this, &PoseEditWidget::parametersAdjusted, this, &PoseEditWidget::updatePreview); connect(this, &PoseEditWidget::parametersAdjusted, [=]() { m_unsaved = true; @@ -71,55 +123,43 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : 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); - updateButtons(); - updatePreview(); + updatePoseDocument(); updateTitle(); + m_poseDocument->saveHistoryItem(); } -void PoseEditWidget::updateButtons() +void PoseEditWidget::changeTurnaround() { - delete m_buttonsContainer; - m_buttonsContainer = new QWidget(this); - m_buttonsContainer->resize(600, 500); - m_buttonsContainer->move(256, 0); - m_buttonsContainer->show(); - - QGridLayout *marksContainerLayout = new QGridLayout; - marksContainerLayout->setContentsMargins(0, 0, 0, 0); - marksContainerLayout->setSpacing(2); - - QFont buttonFont; - buttonFont.setWeight(QFont::Light); - buttonFont.setPixelSize(7); - buttonFont.setBold(false); - - std::map> buttons; - const std::vector *rigBones = m_document->resultRigBones(); - if (nullptr != rigBones && !rigBones->empty()) { - for (const auto &bone: *rigBones) { - if (!bone.hasButton) - continue; - QPushButton *buttonWidget = new QPushButton(bone.name); - PopupWidgetType widgetType = bone.buttonParameterType; - buttonWidget->setFont(buttonFont); - buttonWidget->setMaximumWidth(100); - QString boneName = bone.name; - connect(buttonWidget, &QPushButton::clicked, [this, boneName, widgetType]() { - emit showPopupAngleDialog(boneName, widgetType, mapFromGlobal(QCursor::pos())); - }); - marksContainerLayout->addWidget(buttonWidget, bone.button.first, bone.button.second); - } - } - - marksContainerLayout->setSizeConstraint(QLayout::SizeConstraint::SetMinimumSize); - - QVBoxLayout *mainLayouer = new QVBoxLayout; - mainLayouer->addStretch(); - mainLayouer->addLayout(marksContainerLayout); - mainLayouer->addStretch(); - - m_buttonsContainer->setLayout(mainLayouer); + QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(), + tr("Image Files (*.png *.jpg *.bmp)")).trimmed(); + if (fileName.isEmpty()) + return; + QImage image; + if (!image.load(fileName)) + return; + m_imageId = ImageForever::add(&image); + m_attributes["canvasImageId"] = m_imageId.toString(); + 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); + updatePreview(); } void PoseEditWidget::reject() @@ -141,10 +181,6 @@ void PoseEditWidget::closeEvent(QCloseEvent *event) } m_closed = true; hide(); - if (m_openedMenuCount > 0) { - event->ignore(); - return; - } if (m_posePreviewManager->isRendering()) { event->ignore(); return; @@ -160,6 +196,7 @@ QSize PoseEditWidget::sizeHint() const PoseEditWidget::~PoseEditWidget() { delete m_posePreviewManager; + delete m_poseDocument; } void PoseEditWidget::updatePreview() @@ -214,157 +251,6 @@ void PoseEditWidget::updateTitle() setWindowTitle(unifiedWindowTitle(pose->name + (m_unsaved ? "*" : ""))); } -void PoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos) -{ - QMenu popupMenu; - - QWidget *popup = new QWidget; - - QVBoxLayout *layout = new QVBoxLayout; - - if (PopupWidgetType::PitchYawRoll == popupWidgetType) { - FloatNumberWidget *pitchWidget = new FloatNumberWidget; - pitchWidget->setItemName(tr("Pitch")); - pitchWidget->setRange(-180, 180); - pitchWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "pitch").toFloat()); - connect(pitchWidget, &FloatNumberWidget::valueChanged, this, [=](float value) { - m_parameters[boneName]["pitch"] = QString::number(value); - emit parametersAdjusted(); - }); - QPushButton *pitchEraser = new QPushButton(QChar(fa::eraser)); - Theme::initAwesomeMiniButton(pitchEraser); - connect(pitchEraser, &QPushButton::clicked, this, [=]() { - pitchWidget->setValue(0.0); - }); - QHBoxLayout *pitchLayout = new QHBoxLayout; - pitchLayout->addWidget(pitchEraser); - pitchLayout->addWidget(pitchWidget); - layout->addLayout(pitchLayout); - - FloatNumberWidget *yawWidget = new FloatNumberWidget; - yawWidget->setItemName(tr("Yaw")); - yawWidget->setRange(-180, 180); - yawWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "yaw").toFloat()); - connect(yawWidget, &FloatNumberWidget::valueChanged, this, [=](float value) { - m_parameters[boneName]["yaw"] = QString::number(value); - emit parametersAdjusted(); - }); - QPushButton *yawEraser = new QPushButton(QChar(fa::eraser)); - Theme::initAwesomeMiniButton(yawEraser); - connect(yawEraser, &QPushButton::clicked, this, [=]() { - yawWidget->setValue(0.0); - }); - QHBoxLayout *yawLayout = new QHBoxLayout; - yawLayout->addWidget(yawEraser); - yawLayout->addWidget(yawWidget); - layout->addLayout(yawLayout); - - FloatNumberWidget *rollWidget = new FloatNumberWidget; - rollWidget->setItemName(tr("Roll")); - rollWidget->setRange(-180, 180); - rollWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "roll").toFloat()); - connect(rollWidget, &FloatNumberWidget::valueChanged, this, [=](float value) { - m_parameters[boneName]["roll"] = QString::number(value); - emit parametersAdjusted(); - }); - QPushButton *rollEraser = new QPushButton(QChar(fa::eraser)); - Theme::initAwesomeMiniButton(rollEraser); - connect(rollEraser, &QPushButton::clicked, this, [=]() { - rollWidget->setValue(0.0); - }); - QHBoxLayout *rollLayout = new QHBoxLayout; - rollLayout->addWidget(rollEraser); - rollLayout->addWidget(rollWidget); - layout->addLayout(rollLayout); - } else if (PopupWidgetType::Intersection == popupWidgetType) { - FloatNumberWidget *intersectionWidget = new FloatNumberWidget; - intersectionWidget->setItemName(tr("Intersection")); - intersectionWidget->setRange(-180, 180); - intersectionWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "intersection").toFloat()); - connect(intersectionWidget, &FloatNumberWidget::valueChanged, this, [=](float value) { - m_parameters[boneName]["intersection"] = QString::number(value); - emit parametersAdjusted(); - }); - QPushButton *intersectionEraser = new QPushButton(QChar(fa::eraser)); - Theme::initAwesomeMiniButton(intersectionEraser); - connect(intersectionEraser, &QPushButton::clicked, this, [=]() { - intersectionWidget->setValue(0.0); - }); - QHBoxLayout *intersectionLayout = new QHBoxLayout; - intersectionLayout->addWidget(intersectionEraser); - intersectionLayout->addWidget(intersectionWidget); - layout->addLayout(intersectionLayout); - } else if (PopupWidgetType::Translation == popupWidgetType) { - FloatNumberWidget *xWidget = new FloatNumberWidget; - xWidget->setItemName(tr("X")); - xWidget->setRange(-1, 1); - xWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "x").toFloat()); - connect(xWidget, &FloatNumberWidget::valueChanged, this, [=](float value) { - m_parameters[boneName]["x"] = QString::number(value); - emit parametersAdjusted(); - }); - QPushButton *xEraser = new QPushButton(QChar(fa::eraser)); - Theme::initAwesomeMiniButton(xEraser); - connect(xEraser, &QPushButton::clicked, this, [=]() { - xWidget->setValue(0.0); - }); - QHBoxLayout *xLayout = new QHBoxLayout; - xLayout->addWidget(xEraser); - xLayout->addWidget(xWidget); - layout->addLayout(xLayout); - - FloatNumberWidget *yWidget = new FloatNumberWidget; - yWidget->setItemName(tr("Y")); - yWidget->setRange(-1, 1); - yWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "y").toFloat()); - connect(yWidget, &FloatNumberWidget::valueChanged, this, [=](float value) { - m_parameters[boneName]["y"] = QString::number(value); - emit parametersAdjusted(); - }); - QPushButton *yEraser = new QPushButton(QChar(fa::eraser)); - Theme::initAwesomeMiniButton(yEraser); - connect(yEraser, &QPushButton::clicked, this, [=]() { - yWidget->setValue(0.0); - }); - QHBoxLayout *yLayout = new QHBoxLayout; - yLayout->addWidget(yEraser); - yLayout->addWidget(yWidget); - layout->addLayout(yLayout); - - FloatNumberWidget *zWidget = new FloatNumberWidget; - zWidget->setItemName(tr("Z")); - zWidget->setRange(-1, 1); - zWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "z").toFloat()); - connect(zWidget, &FloatNumberWidget::valueChanged, this, [=](float value) { - m_parameters[boneName]["z"] = QString::number(value); - emit parametersAdjusted(); - }); - QPushButton *zEraser = new QPushButton(QChar(fa::eraser)); - Theme::initAwesomeMiniButton(zEraser); - connect(zEraser, &QPushButton::clicked, this, [=]() { - zWidget->setValue(0.0); - }); - QHBoxLayout *zLayout = new QHBoxLayout; - zLayout->addWidget(zEraser); - zLayout->addWidget(zWidget); - layout->addLayout(zLayout); - } - - popup->setLayout(layout); - - QWidgetAction action(this); - action.setDefaultWidget(popup); - - popupMenu.addAction(&action); - - m_openedMenuCount++; - popupMenu.exec(mapToGlobal(pos)); - m_openedMenuCount--; - - if (m_closed) - close(); -} - void PoseEditWidget::setEditPoseName(QString name) { m_nameEdit->setText(name); @@ -374,6 +260,15 @@ void PoseEditWidget::setEditPoseName(QString name) void PoseEditWidget::setEditParameters(std::map> parameters) { m_parameters = parameters; + updatePoseDocument(); + updatePreview(); + m_poseDocument->saveHistoryItem(); +} + +void PoseEditWidget::setEditAttributes(std::map attributes) +{ + m_attributes = attributes; + updatePoseDocument(); updatePreview(); } @@ -390,6 +285,7 @@ void PoseEditWidget::save() } else if (m_unsaved) { emit renamePose(m_poseId, m_nameEdit->text()); emit setPoseParameters(m_poseId, m_parameters); + emit setPoseAttributes(m_poseId, m_attributes); } m_unsaved = false; close(); diff --git a/src/poseeditwidget.h b/src/poseeditwidget.h index 6e022f51..9b8af96c 100644 --- a/src/poseeditwidget.h +++ b/src/poseeditwidget.h @@ -8,6 +8,8 @@ #include "document.h" #include "modelwidget.h" #include "rigger.h" +#include "skeletongraphicswidget.h" +#include "posedocument.h" typedef RiggerButtonParameterType PopupWidgetType; @@ -18,21 +20,23 @@ signals: void addPose(QString name, std::map> parameters); void removePose(QUuid poseId); void setPoseParameters(QUuid poseId, std::map> parameters); + void setPoseAttributes(QUuid poseId, std::map attributes); void renamePose(QUuid poseId, QString name); void parametersAdjusted(); public: PoseEditWidget(const Document *document, QWidget *parent=nullptr); ~PoseEditWidget(); public slots: - void updateButtons(); + void updatePoseDocument(); void updatePreview(); - void showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos); void setEditPoseId(QUuid poseId); void setEditPoseName(QString name); void setEditParameters(std::map> parameters); + void setEditAttributes(std::map attributes); void updateTitle(); void save(); void clearUnsaveState(); + void changeTurnaround(); protected: QSize sizeHint() const override; void closeEvent(QCloseEvent *event) override; @@ -44,11 +48,14 @@ private: bool m_isPreviewDirty = false; bool m_closed = false; std::map> m_parameters; - size_t m_openedMenuCount = 0; + std::map m_attributes; QUuid m_poseId; bool m_unsaved = false; + QUuid m_imageId; QLineEdit *m_nameEdit = nullptr; - QWidget *m_buttonsContainer = nullptr; + PoseDocument *m_poseDocument = nullptr; + SkeletonGraphicsWidget *m_poseGraphicsWidget = nullptr; + QUuid findImageIdFromAttributes(const std::map &attributes); }; #endif diff --git a/src/posemanagewidget.cpp b/src/posemanagewidget.cpp index e03f0627..2e5d2f65 100644 --- a/src/posemanagewidget.cpp +++ b/src/posemanagewidget.cpp @@ -22,14 +22,21 @@ PoseManageWidget::PoseManageWidget(const Document *document, QWidget *parent) : connect(m_poseListWidget, &PoseListWidget::modifyPose, this, &PoseManageWidget::showPoseDialog); InfoLabel *infoLabel = new InfoLabel; - infoLabel->setText(tr("Missing Rig")); infoLabel->show(); auto refreshInfoLabel = [=]() { if (m_document->currentRigSucceed()) { - infoLabel->hide(); - addPoseButton->show(); + if (m_document->rigType == RigType::Tetrapod) { + infoLabel->setText(""); + infoLabel->hide(); + addPoseButton->show(); + } else { + infoLabel->setText(tr("Pose editor doesn't support this rig type yet: ") + RigTypeToDispName(m_document->rigType)); + infoLabel->show(); + addPoseButton->hide(); + } } else { + infoLabel->setText(tr("Missing Rig")); infoLabel->show(); addPoseButton->hide(); } @@ -71,6 +78,7 @@ void PoseManageWidget::showPoseDialog(QUuid poseId) poseEditWidget->setEditPoseId(poseId); poseEditWidget->setEditPoseName(pose->name); poseEditWidget->setEditParameters(pose->parameters); + poseEditWidget->setEditAttributes(pose->attributes); poseEditWidget->clearUnsaveState(); } } diff --git a/src/rigger.cpp b/src/rigger.cpp index 00b8bdb3..e25d7ccb 100644 --- a/src/rigger.cpp +++ b/src/rigger.cpp @@ -6,6 +6,7 @@ #include "rigger.h" size_t Rigger::m_maxCutOffSplitterExpandRound = 3; +QString Rigger::rootBoneName = "Body"; Rigger::Rigger(const std::vector &verticesPositions, const std::set &inputTriangles) : diff --git a/src/rigger.h b/src/rigger.h index 183b9258..771edaaa 100644 --- a/src/rigger.h +++ b/src/rigger.h @@ -125,6 +125,7 @@ public: const std::vector &missingMarkNames(); const std::vector &errorMarkNames(); virtual bool rig() = 0; + static QString rootBoneName; protected: virtual bool validate() = 0; virtual bool isCutOffSplitter(BoneMark boneMark) = 0; diff --git a/src/shortcuts.cpp b/src/shortcuts.cpp index 08407a2c..edc36ebe 100644 --- a/src/shortcuts.cpp +++ b/src/shortcuts.cpp @@ -3,13 +3,13 @@ #include "shortcuts.h" #define defineKey(keyVal, funcName) do { \ - auto key = new QShortcut(mainWindow); \ + auto key = new QShortcut(widget); \ key->setKey(keyVal); \ QObject::connect(key, &QShortcut::activated, \ graphicsWidget, funcName); \ } while (false) -void initShortCuts(QMainWindow *mainWindow, SkeletonGraphicsWidget *graphicsWidget) +void initShortCuts(QWidget *widget, SkeletonGraphicsWidget *graphicsWidget) { defineKey(Qt::Key_Delete, &SkeletonGraphicsWidget::shortcutDelete); defineKey(Qt::Key_Backspace, &SkeletonGraphicsWidget::shortcutDelete); diff --git a/src/shortcuts.h b/src/shortcuts.h index 9e48ce1c..426ae9c5 100644 --- a/src/shortcuts.h +++ b/src/shortcuts.h @@ -3,6 +3,6 @@ #include #include "skeletongraphicswidget.h" -void initShortCuts(QMainWindow *mainWindow, SkeletonGraphicsWidget *graphicsWidget); +void initShortCuts(QWidget *widget, SkeletonGraphicsWidget *graphicsWidget); #endif diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index 858d0c3e..e806aaf1 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -1,2 +1,73 @@ +#include #include "skeletondocument.h" +const SkeletonNode *SkeletonDocument::findNode(QUuid nodeId) const +{ + auto it = nodeMap.find(nodeId); + if (it == nodeMap.end()) + return nullptr; + return &it->second; +} + +const SkeletonEdge *SkeletonDocument::findEdge(QUuid edgeId) const +{ + auto it = edgeMap.find(edgeId); + if (it == edgeMap.end()) + return nullptr; + return &it->second; +} + +const SkeletonPart *SkeletonDocument::findPart(QUuid partId) const +{ + auto it = partMap.find(partId); + if (it == partMap.end()) + return nullptr; + return &it->second; +} + +const SkeletonEdge *SkeletonDocument::findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const +{ + const SkeletonNode *firstNode = nullptr; + firstNode = findNode(firstNodeId); + if (nullptr == firstNode) { + qDebug() << "Find node failed:" << firstNodeId; + return nullptr; + } + for (auto edgeIdIt = firstNode->edgeIds.begin(); edgeIdIt != firstNode->edgeIds.end(); edgeIdIt++) { + auto edgeIt = edgeMap.find(*edgeIdIt); + if (edgeIt == edgeMap.end()) { + qDebug() << "Find edge failed:" << *edgeIdIt; + continue; + } + if (std::find(edgeIt->second.nodeIds.begin(), edgeIt->second.nodeIds.end(), secondNodeId) != edgeIt->second.nodeIds.end()) + return &edgeIt->second; + } + return nullptr; +} + +void SkeletonDocument::findAllNeighbors(QUuid nodeId, std::set &neighbors) const +{ + const auto &node = findNode(nodeId); + if (nullptr == node) { + qDebug() << "findNode:" << nodeId << "failed"; + return; + } + for (const auto &edgeId: node->edgeIds) { + const auto &edge = findEdge(edgeId); + if (nullptr == edge) { + qDebug() << "findEdge:" << edgeId << "failed"; + continue; + } + const auto &neighborNodeId = edge->neighborOf(nodeId); + if (neighborNodeId.isNull()) { + qDebug() << "neighborOf:" << nodeId << "is null from edge:" << edgeId; + continue; + } + if (neighbors.find(neighborNodeId) != neighbors.end()) { + continue; + } + neighbors.insert(neighborNodeId); + findAllNeighbors(neighborNodeId, neighbors); + } +} + diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 24c5e090..6050a81d 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -200,18 +200,22 @@ public: bool zlocked = false; bool radiusLocked = false; QImage turnaround; + std::map partMap; + std::map nodeMap; + std::map edgeMap; - virtual const SkeletonNode *findNode(QUuid nodeId) const = 0; - virtual const SkeletonEdge *findEdge(QUuid edgeId) const = 0; - virtual const SkeletonPart *findPart(QUuid partId) const = 0; - virtual const SkeletonEdge *findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const = 0; + const SkeletonNode *findNode(QUuid nodeId) const; + const SkeletonEdge *findEdge(QUuid edgeId) const; + const SkeletonPart *findPart(QUuid partId) const; + const SkeletonEdge *findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const; + void findAllNeighbors(QUuid nodeId, std::set &neighbors) const; + virtual bool undoable() const = 0; virtual bool redoable() const = 0; virtual bool hasPastableNodesInClipboard() const = 0; virtual bool originSettled() const = 0; virtual bool isNodeEditable(QUuid nodeId) const = 0; virtual bool isEdgeEditable(QUuid edgeId) const = 0; - virtual void findAllNeighbors(QUuid nodeId, std::set &neighbors) const = 0; virtual void copyNodes(std::set nodeIdSet) const = 0; public slots: diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index d49ae1ff..5eb9ec27 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -44,7 +44,8 @@ SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document) m_eventForwardingToModelWidget(false), m_modelWidget(nullptr), m_inTempDragMode(false), - m_modeBeforeEnterTempDragMode(SkeletonDocumentEditMode::Select) + m_modeBeforeEnterTempDragMode(SkeletonDocumentEditMode::Select), + m_nodePositionModifyOnly(false) { setRenderHint(QPainter::Antialiasing, false); setBackgroundBrush(QBrush(QWidget::palette().color(QWidget::backgroundRole()), Qt::SolidPattern)); @@ -118,10 +119,12 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos) QMenu contextMenu(this); QAction addAction(tr("Add..."), this); - connect(&addAction, &QAction::triggered, [=]() { - emit setEditMode(SkeletonDocumentEditMode::Add); - }); - contextMenu.addAction(&addAction); + if (!m_nodePositionModifyOnly) { + connect(&addAction, &QAction::triggered, [=]() { + emit setEditMode(SkeletonDocumentEditMode::Add); + }); + contextMenu.addAction(&addAction); + } QAction undoAction(tr("Undo"), this); if (m_document->undoable()) { @@ -136,67 +139,67 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos) } QAction deleteAction(tr("Delete"), this); - if (hasSelection()) { + if (!m_nodePositionModifyOnly && hasSelection()) { connect(&deleteAction, &QAction::triggered, this, &SkeletonGraphicsWidget::deleteSelected); contextMenu.addAction(&deleteAction); } QAction breakAction(tr("Break"), this); - if (hasEdgeSelection()) { + if (!m_nodePositionModifyOnly && hasEdgeSelection()) { connect(&breakAction, &QAction::triggered, this, &SkeletonGraphicsWidget::breakSelected); contextMenu.addAction(&breakAction); } QAction connectAction(tr("Connect"), this); - if (hasTwoDisconnectedNodesSelection()) { + if (!m_nodePositionModifyOnly && hasTwoDisconnectedNodesSelection()) { connect(&connectAction, &QAction::triggered, this, &SkeletonGraphicsWidget::connectSelected); contextMenu.addAction(&connectAction); } QAction cutAction(tr("Cut"), this); - if (hasSelection()) { + if (!m_nodePositionModifyOnly && hasSelection()) { connect(&cutAction, &QAction::triggered, this, &SkeletonGraphicsWidget::cut); contextMenu.addAction(&cutAction); } QAction copyAction(tr("Copy"), this); - if (hasSelection()) { + if (!m_nodePositionModifyOnly && hasSelection()) { connect(©Action, &QAction::triggered, this, &SkeletonGraphicsWidget::copy); contextMenu.addAction(©Action); } QAction pasteAction(tr("Paste"), this); - if (m_document->hasPastableNodesInClipboard()) { + if (!m_nodePositionModifyOnly && m_document->hasPastableNodesInClipboard()) { connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste); contextMenu.addAction(&pasteAction); } QAction flipHorizontallyAction(tr("H Flip"), this); - if (hasMultipleSelection()) { + if (!m_nodePositionModifyOnly && hasMultipleSelection()) { connect(&flipHorizontallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipHorizontally); contextMenu.addAction(&flipHorizontallyAction); } QAction flipVerticallyAction(tr("V Flip"), this); - if (hasMultipleSelection()) { + if (!m_nodePositionModifyOnly && hasMultipleSelection()) { connect(&flipVerticallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipVertically); contextMenu.addAction(&flipVerticallyAction); } QAction rotateClockwiseAction(tr("Rotate 90D CW"), this); - if (hasMultipleSelection()) { + if (!m_nodePositionModifyOnly && hasMultipleSelection()) { connect(&rotateClockwiseAction, &QAction::triggered, this, &SkeletonGraphicsWidget::rotateClockwise90Degree); contextMenu.addAction(&rotateClockwiseAction); } QAction rotateCounterclockwiseAction(tr("Rotate 90D CCW"), this); - if (hasMultipleSelection()) { + if (!m_nodePositionModifyOnly && hasMultipleSelection()) { connect(&rotateCounterclockwiseAction, &QAction::triggered, this, &SkeletonGraphicsWidget::rotateCounterclockwise90Degree); contextMenu.addAction(&rotateCounterclockwiseAction); } QAction switchXzAction(tr("Switch XZ"), this); - if (hasSelection()) { + if (!m_nodePositionModifyOnly && hasSelection()) { connect(&switchXzAction, &QAction::triggered, this, &SkeletonGraphicsWidget::switchSelectedXZ); contextMenu.addAction(&switchXzAction); } @@ -207,7 +210,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos) QAction alignToGlobalCenterAction(tr("Global Center"), this); QAction alignToGlobalVerticalCenterAction(tr("Global Vertical Center"), this); QAction alignToGlobalHorizontalCenterAction(tr("Global Horizontal Center"), this); - if ((hasSelection() && m_document->originSettled()) || hasMultipleSelection()) { + if (!m_nodePositionModifyOnly && ((hasSelection() && m_document->originSettled()) || hasMultipleSelection())) { QMenu *subMenu = contextMenu.addMenu(tr("Align To")); if (hasMultipleSelection()) { @@ -238,7 +241,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos) for (int i = 0; i < (int)BoneMark::Count - 1; i++) { markAsActions[i] = nullptr; } - if (hasNodeSelection()) { + if (!m_nodePositionModifyOnly && hasNodeSelection()) { QMenu *subMenu = contextMenu.addMenu(tr("Mark As")); connect(&markAsNoneAction, &QAction::triggered, [=]() { @@ -265,7 +268,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos) } QAction selectPartAllAction(tr("Select Part"), this); - if (hasItems()) { + if (!m_nodePositionModifyOnly && hasItems()) { connect(&selectPartAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::selectPartAll); contextMenu.addAction(&selectPartAllAction); } @@ -2456,3 +2459,8 @@ void SkeletonGraphicsWidget::setSelectedNodesBoneMark(BoneMark boneMark) emit groupOperationAdded(); } } + +void SkeletonGraphicsWidget::setNodePositionModifyOnly(bool nodePositionModifyOnly) +{ + m_nodePositionModifyOnly = nodePositionModifyOnly; +} diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index f4db94f6..1d2c593d 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -408,6 +408,7 @@ public: bool hasNodeSelection(); bool hasTwoDisconnectedNodesSelection(); void setModelWidget(ModelWidget *modelWidget); + void setNodePositionModifyOnly(bool nodePositionModifyOnly); protected: void mouseMoveEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; @@ -557,6 +558,7 @@ private: //need initalize ModelWidget *m_modelWidget; bool m_inTempDragMode; SkeletonDocumentEditMode m_modeBeforeEnterTempDragMode; + bool m_nodePositionModifyOnly; private: QVector3D m_ikMoveTarget; QUuid m_ikMoveEndEffectorId; diff --git a/src/tetrapodposer.cpp b/src/tetrapodposer.cpp index d9203515..f8c09204 100644 --- a/src/tetrapodposer.cpp +++ b/src/tetrapodposer.cpp @@ -1,3 +1,5 @@ +#include +#include #include "tetrapodposer.h" #include "util.h" @@ -6,79 +8,211 @@ TetrapodPoser::TetrapodPoser(const std::vector &bones) : { } -void TetrapodPoser::commit() +void TetrapodPoser::resolveTranslation() { for (const auto &item: parameters()) { int boneIndex = findBoneIndex(item.first); if (-1 == boneIndex) { continue; } - auto findPitchResult = item.second.find("pitch"); - auto findYawResult = item.second.find("yaw"); - auto findRollResult = item.second.find("roll"); - if (findPitchResult != item.second.end() || - findYawResult != item.second.end() || - findRollResult != item.second.end()) { - float yawAngle = valueOfKeyInMapOrEmpty(item.second, "yaw").toFloat(); - if (item.first.startsWith("Left")) { - yawAngle = -yawAngle; - } - QQuaternion rotation = eulerAnglesToQuaternion(valueOfKeyInMapOrEmpty(item.second, "pitch").toFloat(), - yawAngle, - valueOfKeyInMapOrEmpty(item.second, "roll").toFloat()); - m_jointNodeTree.updateRotation(boneIndex, rotation); - continue; - } - auto findIntersectionResult = item.second.find("intersection"); - if (findIntersectionResult != item.second.end()) { - float intersectionAngle = valueOfKeyInMapOrEmpty(item.second, "intersection").toFloat(); - const RiggerBone &bone = bones()[boneIndex]; - if (bone.name == "LeftHand" || bone.name == "RightHand") { - QVector3D handDirection = bone.tailPosition - bone.headPosition; - QVector3D referenceDirection = bone.name == "RightHand" ? QVector3D(1, 0, 0) : QVector3D(-1, 0, 0); - auto angleWithX = (int)angleBetweenVectors(handDirection, -referenceDirection); - auto angleWithZ = (int)angleBetweenVectors(handDirection, QVector3D(0, 0, -1)); - QVector3D rotateAxis = angleWithX < angleWithZ ? - QVector3D::crossProduct(handDirection, referenceDirection).normalized() : - QVector3D::crossProduct(handDirection, QVector3D(0, 0, -1)).normalized(); - QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle); - m_jointNodeTree.updateRotation(boneIndex, rotation); - continue; - } else if (bone.name == "LeftLowerArm" || bone.name == "RightLowerArm") { - QVector3D lowerArmDirection = bone.tailPosition - bone.headPosition; - QVector3D rotateAxis = QVector3D::crossProduct(lowerArmDirection, QVector3D(0, 0, 1)).normalized(); - QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle); - m_jointNodeTree.updateRotation(boneIndex, rotation); - continue; - } else if (bone.name == "LeftFoot" || bone.name == "RightFoot") { - QVector3D footDirection = bone.tailPosition - bone.headPosition; - QVector3D rotateAxis = QVector3D::crossProduct(footDirection, QVector3D(0, 1, 0)).normalized(); - QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle); - m_jointNodeTree.updateRotation(boneIndex, rotation); - continue; - } else if (bone.name == "LeftLowerLeg" || bone.name == "RightLowerLeg") { - QVector3D lowerLegDirection = bone.tailPosition - bone.headPosition; - QVector3D rotateAxis = QVector3D::crossProduct(lowerLegDirection, QVector3D(0, 0, -1)).normalized(); - QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle); - m_jointNodeTree.updateRotation(boneIndex, rotation); - continue; - } - continue; - } - auto findXResult = item.second.find("x"); - auto findYResult = item.second.find("y"); - auto findZResult = item.second.find("z"); - if (findXResult != item.second.end() || - findYResult != item.second.end() || - findZResult != item.second.end()) { - float x = valueOfKeyInMapOrEmpty(item.second, "x").toFloat(); - float y = valueOfKeyInMapOrEmpty(item.second, "y").toFloat(); - float z = valueOfKeyInMapOrEmpty(item.second, "z").toFloat(); + auto findTranslateXResult = item.second.find("translateX"); + auto findTranslateYResult = item.second.find("translateY"); + auto findTranslateZResult = item.second.find("translateZ"); + if (findTranslateXResult != item.second.end() || + findTranslateYResult != item.second.end() || + findTranslateZResult != item.second.end()) { + float x = valueOfKeyInMapOrEmpty(item.second, "translateX").toFloat(); + float y = valueOfKeyInMapOrEmpty(item.second, "translateY").toFloat(); + float z = valueOfKeyInMapOrEmpty(item.second, "translateZ").toFloat(); QVector3D translation = {x, y, z}; m_jointNodeTree.addTranslation(boneIndex, translation); continue; } } + resolveLimbRotation({QString("LeftUpperArm"), QString("LeftLowerArm"), QString("LeftHand")}); + resolveLimbRotation({QString("RightUpperArm"), QString("RightLowerArm"), QString("RightHand")}); + resolveLimbRotation({QString("LeftUpperLeg"), QString("LeftLowerLeg"), QString("LeftFoot")}); + resolveLimbRotation({QString("RightUpperLeg"), QString("RightLowerLeg"), QString("RightFoot")}); + resolveLimbRotation({QString("Spine"), QString("Chest"), QString("Neck"), QString("Head")}); +} + +std::pair TetrapodPoser::findQVector3DFromMap(const std::map &map, const QString &xName, const QString &yName, const QString &zName) +{ + auto findXResult = map.find(xName); + auto findYResult = map.find(yName); + auto findZResult = map.find(zName); + if (findXResult == map.end() && + findYResult == map.end() && + findZResult == map.end()) { + return {false, QVector3D()}; + } + return {true, { + valueOfKeyInMapOrEmpty(map, xName).toFloat(), + valueOfKeyInMapOrEmpty(map, yName).toFloat(), + valueOfKeyInMapOrEmpty(map, zName).toFloat() + }}; +} + +std::pair> TetrapodPoser::findBonePositionsFromParameters(const std::map &map) +{ + auto findBoneStartResult = findQVector3DFromMap(map, "fromX", "fromY", "fromZ"); + auto findBoneStopResult = findQVector3DFromMap(map, "toX", "toY", "toZ"); + + if (!findBoneStartResult.first || !findBoneStopResult.first) + return {false, {QVector3D(), QVector3D()}}; + + return {true, {findBoneStartResult.second, findBoneStopResult.second}}; +} + +void TetrapodPoser::resolveLimbRotation(const std::vector &limbBoneNames) +{ + // We match the poses by the distance and rotation plane + + if (limbBoneNames.size() < 3) { + qDebug() << "Cann't resolve limb bones with invalid joints:" << limbBoneNames.size(); + return; + } + const auto &beginBoneName = limbBoneNames[0]; + const auto &middleBoneName = limbBoneNames[1]; + const auto &endBoneName = limbBoneNames[2]; + + const auto &beginBoneParameters = parameters().find(beginBoneName); + if (beginBoneParameters == parameters().end()) { + qDebug() << beginBoneName << "'s parameters not found"; + return; + } + + auto matchBeginBonePositions = findBonePositionsFromParameters(beginBoneParameters->second); + if (!matchBeginBonePositions.first) { + qDebug() << beginBoneName << "'s positions not found"; + return; + } + + const auto &endBoneParameters = parameters().find(endBoneName); + if (endBoneParameters == parameters().end()) { + qDebug() << endBoneName << "'s parameters not found"; + return; + } + + auto matchEndBonePositions = findBonePositionsFromParameters(endBoneParameters->second); + if (!matchEndBonePositions.first) { + qDebug() << endBoneName << "'s positions not found"; + return; + } + + float matchLimbLength = (matchBeginBonePositions.second.first - matchBeginBonePositions.second.second).length() + + (matchBeginBonePositions.second.second - matchEndBonePositions.second.first).length(); + + auto matchDistanceBetweenBeginAndEndBones = (matchBeginBonePositions.second.first - matchEndBonePositions.second.first).length(); + auto matchRotatePlaneNormal = QVector3D::crossProduct((matchBeginBonePositions.second.second - matchBeginBonePositions.second.first).normalized(), (matchEndBonePositions.second.first - matchBeginBonePositions.second.second).normalized()); + + auto matchDirectionBetweenBeginAndEndPones = (matchEndBonePositions.second.first - matchBeginBonePositions.second.first).normalized(); + + auto matchEndBoneDirection = (matchEndBonePositions.second.second - matchEndBonePositions.second.first).normalized(); + + int beginBoneIndex = findBoneIndex(beginBoneName); + if (-1 == beginBoneIndex) { + qDebug() << beginBoneName << "not found in rigged bones"; + return; + } + const auto &beginBone = bones()[beginBoneIndex]; + + int middleBoneIndex = findBoneIndex(middleBoneName); + if (-1 == middleBoneIndex) { + qDebug() << middleBoneName << "not found in rigged bones"; + return; + } + + int endBoneIndex = findBoneIndex(endBoneName); + if (-1 == endBoneIndex) { + qDebug() << endBoneName << "not found in rigged bones"; + return; + } + const auto &endBone = bones()[endBoneIndex]; + + float targetBeginBoneLength = (beginBone.headPosition - beginBone.tailPosition).length(); + float targetMiddleBoneLength = (beginBone.tailPosition - endBone.headPosition).length(); + float targetLimbLength = targetBeginBoneLength + targetMiddleBoneLength; + + float targetDistanceBetweenBeginAndEndBones = matchDistanceBetweenBeginAndEndBones * (targetLimbLength / matchLimbLength); + QVector3D targetEndBoneStartPosition = beginBone.headPosition + matchDirectionBetweenBeginAndEndPones * targetDistanceBetweenBeginAndEndBones; + + float angleBetweenDistanceAndMiddleBones = 0; + { + const float &a = targetMiddleBoneLength; + const float &b = targetDistanceBetweenBeginAndEndBones; + const float &c = targetBeginBoneLength; + double cosC = (a*a + b*b - c*c) / (2.0*a*b); + angleBetweenDistanceAndMiddleBones = qRadiansToDegrees(acos(cosC)); + } + + QVector3D targetMiddleBoneStartPosition; + { + qDebug() << beginBoneName << "Angle:" << angleBetweenDistanceAndMiddleBones; + auto rotation = QQuaternion::fromAxisAndAngle(matchRotatePlaneNormal, angleBetweenDistanceAndMiddleBones); + targetMiddleBoneStartPosition = targetEndBoneStartPosition + rotation.rotatedVector(-matchDirectionBetweenBeginAndEndPones).normalized() * targetMiddleBoneLength; + } + + // Now the bones' positions have been resolved, we calculate the rotation + + auto oldBeginBoneDirection = (beginBone.tailPosition - beginBone.headPosition).normalized(); + auto newBeginBoneDirection = (targetMiddleBoneStartPosition - beginBone.headPosition).normalized(); + auto beginBoneRotation = QQuaternion::rotationTo(oldBeginBoneDirection, newBeginBoneDirection); + m_jointNodeTree.updateRotation(beginBoneIndex, beginBoneRotation); + + auto oldMiddleBoneDirection = (endBone.headPosition - beginBone.tailPosition).normalized(); + auto newMiddleBoneDirection = (targetEndBoneStartPosition - targetMiddleBoneStartPosition).normalized(); + oldMiddleBoneDirection = beginBoneRotation.rotatedVector(oldMiddleBoneDirection); + auto middleBoneRotation = QQuaternion::rotationTo(oldMiddleBoneDirection, newMiddleBoneDirection); + m_jointNodeTree.updateRotation(middleBoneIndex, middleBoneRotation); + + // Calculate the end effectors' rotation + auto oldEndBoneDirection = (endBone.tailPosition - endBone.headPosition).normalized(); + auto newEndBoneDirection = matchEndBoneDirection; + oldEndBoneDirection = beginBoneRotation.rotatedVector(oldEndBoneDirection); + oldEndBoneDirection = middleBoneRotation.rotatedVector(oldEndBoneDirection); + auto endBoneRotation = QQuaternion::rotationTo(oldEndBoneDirection, newEndBoneDirection); + m_jointNodeTree.updateRotation(endBoneIndex, endBoneRotation); + + if (limbBoneNames.size() > 3) { + std::vector rotations; + rotations.push_back(beginBoneRotation); + rotations.push_back(middleBoneRotation); + rotations.push_back(endBoneRotation); + for (size_t i = 3; i < limbBoneNames.size(); ++i) { + const auto &boneName = limbBoneNames[i]; + int boneIndex = findBoneIndex(boneName); + if (-1 == boneIndex) { + qDebug() << "Find bone failed:" << boneName; + continue; + } + const auto &bone = bones()[boneIndex]; + const auto &boneParameters = parameters().find(boneName); + if (boneParameters == parameters().end()) { + qDebug() << "Find bone parameters:" << boneName; + continue; + } + auto matchBonePositions = findBonePositionsFromParameters(boneParameters->second); + if (!matchBonePositions.first) { + qDebug() << "Find bone positions failed:" << boneName; + continue; + } + auto matchBoneDirection = (matchBonePositions.second.second - matchBonePositions.second.first).normalized(); + auto oldBoneDirection = (bone.tailPosition - bone.headPosition).normalized(); + auto newBoneDirection = matchBoneDirection; + for (const auto &rotation: rotations) { + oldBoneDirection = rotation.rotatedVector(oldBoneDirection); + } + auto boneRotation = QQuaternion::rotationTo(oldBoneDirection, newBoneDirection); + m_jointNodeTree.updateRotation(boneIndex, boneRotation); + rotations.push_back(boneRotation); + } + } +} + +void TetrapodPoser::commit() +{ + resolveTranslation(); Poser::commit(); } diff --git a/src/tetrapodposer.h b/src/tetrapodposer.h index 18268ada..73d4eba1 100644 --- a/src/tetrapodposer.h +++ b/src/tetrapodposer.h @@ -1,5 +1,6 @@ #ifndef DUST3D_TETRAPOD_POSER_H #define DUST3D_TETRAPOD_POSER_H +#include #include "poser.h" class TetrapodPoser : public Poser @@ -7,8 +8,13 @@ class TetrapodPoser : public Poser Q_OBJECT public: TetrapodPoser(const std::vector &bones); -public: void commit() override; + +private: + void resolveTranslation(); + void resolveLimbRotation(const std::vector &limbBoneNames); + std::pair findQVector3DFromMap(const std::map &map, const QString &xName, const QString &yName, const QString &zName); + std::pair> findBonePositionsFromParameters(const std::map &map); }; #endif diff --git a/src/tetrapodrigger.cpp b/src/tetrapodrigger.cpp index 27e61bab..bfbd0de9 100644 --- a/src/tetrapodrigger.cpp +++ b/src/tetrapodrigger.cpp @@ -371,7 +371,7 @@ bool TetrapodRigger::rig() m_resultBones.push_back(RiggerBone()); RiggerBone &bodyBone = m_resultBones.back(); bodyBone.index = m_resultBones.size() - 1; - bodyBone.name = "Body"; + bodyBone.name = Rigger::rootBoneName; bodyBone.headPosition = QVector3D(0, 0, 0); bodyBone.tailPosition = bonesOrigin; bodyBone.hasButton = true; @@ -383,10 +383,10 @@ bool TetrapodRigger::rig() RiggerBone &leftHipBone = m_resultBones.back(); leftHipBone.index = m_resultBones.size() - 1; leftHipBone.name = "LeftHip"; - leftHipBone.headPosition = m_resultBones[boneIndexMap["Body"]].tailPosition; + leftHipBone.headPosition = m_resultBones[boneIndexMap[Rigger::rootBoneName]].tailPosition; leftHipBone.tailPosition = leftUpperLegBoneStartPosition; boneIndexMap[leftHipBone.name] = leftHipBone.index; - m_resultBones[boneIndexMap["Body"]].children.push_back(leftHipBone.index); + m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(leftHipBone.index); m_resultBones.push_back(RiggerBone()); RiggerBone &leftUpperLegBone = m_resultBones.back(); @@ -431,10 +431,10 @@ bool TetrapodRigger::rig() RiggerBone &rightHipBone = m_resultBones.back(); rightHipBone.index = m_resultBones.size() - 1; rightHipBone.name = "RightHip"; - rightHipBone.headPosition = m_resultBones[boneIndexMap["Body"]].tailPosition; + rightHipBone.headPosition = m_resultBones[boneIndexMap[Rigger::rootBoneName]].tailPosition; rightHipBone.tailPosition = rightUpperLegBoneStartPosition; boneIndexMap[rightHipBone.name] = rightHipBone.index; - m_resultBones[boneIndexMap["Body"]].children.push_back(rightHipBone.index); + m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(rightHipBone.index); m_resultBones.push_back(RiggerBone()); RiggerBone &rightUpperLegBone = m_resultBones.back(); @@ -479,14 +479,14 @@ bool TetrapodRigger::rig() RiggerBone &spineBone = m_resultBones.back(); spineBone.index = m_resultBones.size() - 1; spineBone.name = "Spine"; - spineBone.headPosition = m_resultBones[boneIndexMap["Body"]].tailPosition; + spineBone.headPosition = m_resultBones[boneIndexMap[Rigger::rootBoneName]].tailPosition; spineBone.tailPosition = chestBoneStartPosition; spineBone.color = Qt::white; spineBone.hasButton = true; spineBone.button = {3, 1}; spineBone.buttonParameterType = RiggerButtonParameterType::PitchYawRoll; boneIndexMap[spineBone.name] = spineBone.index; - m_resultBones[boneIndexMap["Body"]].children.push_back(spineBone.index); + m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(spineBone.index); m_resultBones.push_back(RiggerBone()); RiggerBone &chestBone = m_resultBones.back();