Add poses list view

Added Add, modify, delete, copy, and paste poses. The paste menu for nodes not show up also got fixed in this commit.
master
Jeremy Hu 2018-09-21 15:10:18 +08:00
parent f163522aa2
commit e22a6de6cb
34 changed files with 1451 additions and 245 deletions

View File

@ -179,8 +179,20 @@ HEADERS += src/posemeshcreator.h
SOURCES += src/posepreviewmanager.cpp
HEADERS += src/posepreviewmanager.h
SOURCES += src/tetrapodposeeditwidget.cpp
HEADERS += src/tetrapodposeeditwidget.h
SOURCES += src/poseeditwidget.cpp
HEADERS += src/poseeditwidget.h
SOURCES += src/poselistwidget.cpp
HEADERS += src/poselistwidget.h
SOURCES += src/posemanagewidget.cpp
HEADERS += src/posemanagewidget.h
SOURCES += src/posepreviewsgenerator.cpp
HEADERS += src/posepreviewsgenerator.h
SOURCES += src/posewidget.cpp
HEADERS += src/posewidget.h
SOURCES += src/main.cpp

View File

@ -3,6 +3,7 @@
#include <QOpenGLFunctions>
#include "aboutwidget.h"
#include "version.h"
#include "dust3dutil.h"
AboutWidget::AboutWidget()
{
@ -16,5 +17,5 @@ AboutWidget::AboutWidget()
setLayout(mainLayout);
setFixedSize(QSize(350, 75));
setWindowTitle(APP_NAME);
setWindowTitle(unifiedWindowTitle(tr("About")));
}

View File

@ -1,8 +1,8 @@
#ifndef ABOUT_WIDGET_H
#define ABOUT_WIDGET_H
#include <QWidget>
#include <QDialog>
class AboutWidget : public QWidget
class AboutWidget : public QDialog
{
Q_OBJECT
public:

View File

@ -1,5 +1,6 @@
#include <cmath>
#include "dust3dutil.h"
#include "version.h"
QString valueOfKeyInMapOrEmpty(const std::map<QString, QString> &map, const QString &key)
{
@ -51,3 +52,7 @@ QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal)
return line - verticalOffset;
}
QString unifiedWindowTitle(const QString &text)
{
return text + QObject::tr(" - ") + APP_NAME;
}

View File

@ -17,5 +17,6 @@ void qNormalizeAngle(int &angle);
QVector3D pointInHermiteCurve(float t, QVector3D p0, QVector3D m0, QVector3D p1, QVector3D m1);
float angleInRangle360BetweenTwoVectors(QVector3D a, QVector3D b, QVector3D planeNormal);
QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal);
QString unifiedWindowTitle(const QString &text);
#endif

View File

@ -7,9 +7,10 @@
#include "aboutwidget.h"
#include "version.h"
#include "theme.h"
#include "dust3dutil.h"
ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *parent) :
QWidget(parent),
QDialog(parent),
m_document(document),
m_previewLabel(nullptr),
m_spinnerWidget(nullptr)
@ -33,6 +34,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
m_saveButton = new QPushButton(tr("Save"));
connect(m_saveButton, &QPushButton::clicked, this, &ExportPreviewWidget::save);
m_saveButton->hide();
m_saveButton->setDefault(true);
QComboBox *exportFormatSelectBox = new QComboBox;
exportFormatSelectBox->addItem(tr("glTF"));
@ -59,11 +61,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
renderLayout->setContentsMargins(20, 0, 20, 0);
renderLayout->addWidget(m_textureRenderWidget);
QWidget *hrLightWidget = new QWidget;
hrLightWidget->setFixedHeight(1);
hrLightWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
hrLightWidget->setStyleSheet(QString("background-color: #565656;"));
hrLightWidget->setContentsMargins(0, 0, 0, 0);
QWidget *hrLightWidget = Theme::createHorizontalLineWidget();
QHBoxLayout *topLayout = new QHBoxLayout;
topLayout->setSpacing(0);
@ -86,7 +84,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
m_spinnerWidget->setNumberOfLines(12);
m_spinnerWidget->hide();
setWindowTitle(tr("Export") + tr(" - ") + APP_NAME);
setWindowTitle(unifiedWindowTitle(tr("Export")));
emit updateTexturePreview();
}

View File

@ -1,6 +1,6 @@
#ifndef EXPORT_PREVIEW_WIDGET_H
#define EXPORT_PREVIEW_WIDGET_H
#include <QWidget>
#include <QDialog>
#include <QImage>
#include <QLabel>
#include <QPushButton>
@ -9,7 +9,7 @@
#include "waitingspinnerwidget.h"
#include "skeletondocument.h"
class ExportPreviewWidget : public QWidget
class ExportPreviewWidget : public QDialog
{
Q_OBJECT
signals:

View File

@ -12,6 +12,7 @@
#include <QCloseEvent>
#include <QKeyEvent>
#include "version.h"
#include "dust3dutil.h"
LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
QDialog(parent)
@ -40,7 +41,7 @@ LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
resize(400, 300);
setWindowTitle(tr("Debug") + tr(" - ") + APP_NAME);
setWindowTitle(unifiedWindowTitle(tr("Debug")));
hide();
}

View File

@ -434,7 +434,7 @@ void *MeshGenerator::combineComponentMesh(QString componentId, bool *inverse)
auto findCachedMesh = m_cacheContext->componentCombinableMeshs.find(componentId);
if (findCachedMesh != m_cacheContext->componentCombinableMeshs.end() &&
nullptr != findCachedMesh->second) {
qDebug() << "Component mesh cache used:" << componentId;
//qDebug() << "Component mesh cache used:" << componentId;
return cloneCombinableMesh(findCachedMesh->second);
}
}
@ -519,7 +519,7 @@ void *MeshGenerator::combineComponentMesh(QString componentId, bool *inverse)
}
}
if (seamVerticesNum > 0) {
qDebug() << "smoothSeamFactor:" << smoothSeamFactor << "seamVerticesIndicies.size():" << seamVerticesNum;
//qDebug() << "smoothSeamFactor:" << smoothSeamFactor << "seamVerticesIndicies.size():" << seamVerticesNum;
meshlite_smooth_vertices(m_meshliteContext, meshIdForSmooth, smoothSeamFactor, seamVerticesIndicies, seamVerticesNum);
}
delete[] seamVerticesIndicies;

View File

@ -1,21 +1,32 @@
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFormLayout>
#include <QGridLayout>
#include <QMenu>
#include <QWidgetAction>
#include <QLineEdit>
#include <QMessageBox>
#include "theme.h"
#include "tetrapodposeeditwidget.h"
#include "poseeditwidget.h"
#include "floatnumberwidget.h"
#include "version.h"
TetrapodPoseEditWidget::TetrapodPoseEditWidget(const SkeletonDocument *document, QWidget *parent) :
QWidget(parent),
PoseEditWidget::PoseEditWidget(const SkeletonDocument *document, QWidget *parent) :
QDialog(parent),
m_document(document)
{
m_posePreviewManager = new PosePreviewManager();
connect(m_posePreviewManager, &PosePreviewManager::renderDone, [=]() {
if (m_closed) {
close();
return;
}
if (m_isPreviewDirty)
updatePreview();
});
connect(m_posePreviewManager, &PosePreviewManager::resultPreviewMeshChanged, [=]() {
m_previewWidget->updateMesh(m_posePreviewManager->takeResultPreviewMesh());
});
std::map<QString, std::tuple<QPushButton *, PopupWidgetType>> buttons;
buttons["Head"] = std::make_tuple(new QPushButton(tr("Head")), PopupWidgetType::PitchYawRoll);
@ -61,36 +72,117 @@ TetrapodPoseEditWidget::TetrapodPoseEditWidget(const SkeletonDocument *document,
QFont buttonFont;
buttonFont.setWeight(QFont::Light);
buttonFont.setPixelSize(7);
buttonFont.setPixelSize(9);
buttonFont.setBold(false);
for (const auto &item: buttons) {
QString boneName = item.first;
QPushButton *buttonWidget = std::get<0>(item.second);
PopupWidgetType widgetType = std::get<1>(item.second);
buttonWidget->setFont(buttonFont);
buttonWidget->setMaximumWidth(45);
buttonWidget->setMaximumWidth(55);
connect(buttonWidget, &QPushButton::clicked, [this, boneName, widgetType]() {
emit showPopupAngleDialog(boneName, widgetType, mapFromGlobal(QCursor::pos()));
});
}
QVBoxLayout *layout = new QVBoxLayout;
layout->addLayout(marksContainerLayout);
layout->addLayout(lowerMarksContainerLayout);
layout->addStretch();
m_previewWidget = new ModelWidget(this);
m_previewWidget->setMinimumSize(128, 128);
m_previewWidget->resize(384, 384);
m_previewWidget->move(-64, -64+22);
setLayout(layout);
QVBoxLayout *markButtonsLayout = new QVBoxLayout;
markButtonsLayout->addStretch();
markButtonsLayout->addLayout(marksContainerLayout);
markButtonsLayout->addLayout(lowerMarksContainerLayout);
markButtonsLayout->addStretch();
connect(m_document, &SkeletonDocument::resultRigChanged, this, &TetrapodPoseEditWidget::updatePreview);
QHBoxLayout *paramtersLayout = new QHBoxLayout;
paramtersLayout->setContentsMargins(256, 0, 0, 0);
paramtersLayout->addStretch();
paramtersLayout->addLayout(markButtonsLayout);
paramtersLayout->addSpacing(20);
m_nameEdit = new QLineEdit;
connect(m_nameEdit, &QLineEdit::textChanged, this, [=]() {
m_unsaved = true;
updateTitle();
});
QPushButton *saveButton = new QPushButton(tr("Save"));
connect(saveButton, &QPushButton::clicked, this, &PoseEditWidget::save);
saveButton->setDefault(true);
QHBoxLayout *baseInfoLayout = new QHBoxLayout;
baseInfoLayout->addWidget(new QLabel(tr("Name")));
baseInfoLayout->addWidget(m_nameEdit);
baseInfoLayout->addStretch();
baseInfoLayout->addWidget(saveButton);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(paramtersLayout);
mainLayout->addWidget(Theme::createHorizontalLineWidget());
mainLayout->addLayout(baseInfoLayout);
setLayout(mainLayout);
connect(m_document, &SkeletonDocument::resultRigChanged, this, &PoseEditWidget::updatePreview);
connect(this, &PoseEditWidget::parametersAdjusted, this, &PoseEditWidget::updatePreview);
connect(this, &PoseEditWidget::parametersAdjusted, [=]() {
m_unsaved = true;
updateTitle();
});
connect(this, &PoseEditWidget::addPose, m_document, &SkeletonDocument::addPose);
connect(this, &PoseEditWidget::renamePose, m_document, &SkeletonDocument::renamePose);
connect(this, &PoseEditWidget::setPoseParameters, m_document, &SkeletonDocument::setPoseParameters);
updatePreview();
updateTitle();
}
TetrapodPoseEditWidget::~TetrapodPoseEditWidget()
void PoseEditWidget::reject()
{
close();
}
void PoseEditWidget::closeEvent(QCloseEvent *event)
{
if (m_unsaved && !m_closed) {
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->ignore();
return;
}
}
m_closed = true;
hide();
if (m_openedMenuCount > 0) {
event->ignore();
return;
}
if (m_posePreviewManager->isRendering()) {
event->ignore();
return;
}
event->accept();
}
QSize PoseEditWidget::sizeHint() const
{
return QSize(0, 350);
}
PoseEditWidget::~PoseEditWidget()
{
delete m_posePreviewManager;
}
void TetrapodPoseEditWidget::updatePreview()
void PoseEditWidget::updatePreview()
{
if (m_closed)
return;
if (m_posePreviewManager->isRendering()) {
m_isPreviewDirty = true;
return;
@ -105,14 +197,37 @@ void TetrapodPoseEditWidget::updatePreview()
return;
}
delete m_poser;
m_poser = new TetrapodPoser(*rigBones);
m_poser->parameters() = m_parameters;
m_poser->commit();
m_posePreviewManager->postUpdate(*m_poser, m_document->currentRiggedResultContext(), *rigWeights);
TetrapodPoser *poser = new TetrapodPoser(*rigBones);
poser->parameters() = m_parameters;
poser->commit();
m_posePreviewManager->postUpdate(*poser, m_document->currentRiggedResultContext(), *rigWeights);
delete poser;
}
void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos)
void PoseEditWidget::setEditPoseId(QUuid poseId)
{
if (m_poseId == poseId)
return;
m_poseId = poseId;
updateTitle();
}
void PoseEditWidget::updateTitle()
{
if (m_poseId.isNull()) {
setWindowTitle(unifiedWindowTitle(tr("New") + (m_unsaved ? "*" : "")));
return;
}
const SkeletonPose *pose = m_document->findPose(m_poseId);
if (nullptr == pose) {
qDebug() << "Find pose failed:" << m_poseId;
return;
}
setWindowTitle(unifiedWindowTitle(pose->name + (m_unsaved ? "*" : "")));
}
void PoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos)
{
QMenu popupMenu;
@ -125,13 +240,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
pitchWidget->setItemName(tr("Pitch"));
pitchWidget->setRange(-180, 180);
pitchWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "pitch").toFloat());
connect(pitchWidget, &FloatNumberWidget::valueChanged, [=](float value) {
connect(pitchWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["pitch"] = QString::number(value);
updatePreview();
emit parametersAdjusted();
});
QPushButton *pitchEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(pitchEraser);
connect(pitchEraser, &QPushButton::clicked, [=]() {
connect(pitchEraser, &QPushButton::clicked, this, [=]() {
pitchWidget->setValue(0.0);
});
QHBoxLayout *pitchLayout = new QHBoxLayout;
@ -143,13 +258,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
yawWidget->setItemName(tr("Yaw"));
yawWidget->setRange(-180, 180);
yawWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "yaw").toFloat());
connect(yawWidget, &FloatNumberWidget::valueChanged, [=](float value) {
connect(yawWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["yaw"] = QString::number(value);
updatePreview();
emit parametersAdjusted();
});
QPushButton *yawEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(yawEraser);
connect(yawEraser, &QPushButton::clicked, [=]() {
connect(yawEraser, &QPushButton::clicked, this, [=]() {
yawWidget->setValue(0.0);
});
QHBoxLayout *yawLayout = new QHBoxLayout;
@ -161,13 +276,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
rollWidget->setItemName(tr("Roll"));
rollWidget->setRange(-180, 180);
rollWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "roll").toFloat());
connect(rollWidget, &FloatNumberWidget::valueChanged, [=](float value) {
connect(rollWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["roll"] = QString::number(value);
updatePreview();
emit parametersAdjusted();
});
QPushButton *rollEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(rollEraser);
connect(rollEraser, &QPushButton::clicked, [=]() {
connect(rollEraser, &QPushButton::clicked, this, [=]() {
rollWidget->setValue(0.0);
});
QHBoxLayout *rollLayout = new QHBoxLayout;
@ -179,13 +294,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
intersectionWidget->setItemName(tr("Intersection"));
intersectionWidget->setRange(-180, 180);
intersectionWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "intersection").toFloat());
connect(intersectionWidget, &FloatNumberWidget::valueChanged, [=](float value) {
connect(intersectionWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["intersection"] = QString::number(value);
updatePreview();
emit parametersAdjusted();
});
QPushButton *intersectionEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(intersectionEraser);
connect(intersectionEraser, &QPushButton::clicked, [=]() {
connect(intersectionEraser, &QPushButton::clicked, this, [=]() {
intersectionWidget->setValue(0.0);
});
QHBoxLayout *intersectionLayout = new QHBoxLayout;
@ -201,5 +316,40 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
popupMenu.addAction(&action);
m_openedMenuCount++;
popupMenu.exec(mapToGlobal(pos));
m_openedMenuCount--;
if (m_closed)
close();
}
void PoseEditWidget::setEditPoseName(QString name)
{
m_nameEdit->setText(name);
updateTitle();
}
void PoseEditWidget::setEditParameters(std::map<QString, std::map<QString, QString>> parameters)
{
m_parameters = parameters;
updatePreview();
}
void PoseEditWidget::clearUnsaveState()
{
m_unsaved = false;
updateTitle();
}
void PoseEditWidget::save()
{
if (m_poseId.isNull()) {
emit addPose(m_nameEdit->text(), m_parameters);
} else if (m_unsaved) {
m_unsaved = false;
emit renamePose(m_poseId, m_nameEdit->text());
emit setPoseParameters(m_poseId, m_parameters);
}
close();
}

56
src/poseeditwidget.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef POSE_EDIT_WIDGET_H
#define POSE_EDIT_WIDGET_H
#include <QDialog>
#include <map>
#include <QCloseEvent>
#include <QLineEdit>
#include "posepreviewmanager.h"
#include "tetrapodposer.h"
#include "skeletondocument.h"
#include "modelwidget.h"
enum class PopupWidgetType
{
PitchYawRoll,
Intersection
};
class PoseEditWidget : public QDialog
{
Q_OBJECT
signals:
void addPose(QString name, std::map<QString, std::map<QString, QString>> parameters);
void removePose(QUuid poseId);
void setPoseParameters(QUuid poseId, std::map<QString, std::map<QString, QString>> parameters);
void renamePose(QUuid poseId, QString name);
void parametersAdjusted();
public:
PoseEditWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
~PoseEditWidget();
public slots:
void updatePreview();
void showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos);
void setEditPoseId(QUuid poseId);
void setEditPoseName(QString name);
void setEditParameters(std::map<QString, std::map<QString, QString>> parameters);
void updateTitle();
void save();
void clearUnsaveState();
protected:
QSize sizeHint() const override;
void closeEvent(QCloseEvent *event) override;
void reject() override;
private:
const SkeletonDocument *m_document = nullptr;
PosePreviewManager *m_posePreviewManager = nullptr;
ModelWidget *m_previewWidget = nullptr;
bool m_isPreviewDirty = false;
bool m_closed = false;
std::map<QString, std::map<QString, QString>> m_parameters;
size_t m_openedMenuCount = 0;
QUuid m_poseId;
bool m_unsaved = false;
QLineEdit *m_nameEdit = nullptr;
};
#endif

316
src/poselistwidget.cpp Normal file
View File

@ -0,0 +1,316 @@
#include <QGuiApplication>
#include <QMenu>
#include <QXmlStreamWriter>
#include <QClipboard>
#include <QApplication>
#include "skeletonxml.h"
#include "poselistwidget.h"
PoseListWidget::PoseListWidget(const SkeletonDocument *document, QWidget *parent) :
QTreeWidget(parent),
m_document(document)
{
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
setFocusPolicy(Qt::NoFocus);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setAutoScroll(false);
setHeaderHidden(true);
QPalette palette = this->palette();
palette.setColor(QPalette::Window, Qt::transparent);
palette.setColor(QPalette::Base, Qt::transparent);
setPalette(palette);
setStyleSheet("QTreeView {qproperty-indentation: 0;}");
setContentsMargins(0, 0, 0, 0);
connect(document, &SkeletonDocument::poseListChanged, this, &PoseListWidget::reload);
connect(document, &SkeletonDocument::cleanup, this, &PoseListWidget::removeAllContent);
connect(this, &PoseListWidget::removePose, document, &SkeletonDocument::removePose);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QTreeWidget::customContextMenuRequested, this, &PoseListWidget::showContextMenu);
reload();
}
void PoseListWidget::poseRemoved(QUuid poseId)
{
if (m_currentSelectedPoseId == poseId)
m_currentSelectedPoseId = QUuid();
m_selectedPoseIds.erase(poseId);
m_itemMap.erase(poseId);
}
void PoseListWidget::updatePoseSelectState(QUuid poseId, bool selected)
{
auto findItemResult = m_itemMap.find(poseId);
if (findItemResult == m_itemMap.end()) {
qDebug() << "Find pose item failed:" << poseId;
return;
}
PoseWidget *poseWidget = (PoseWidget *)itemWidget(findItemResult->second.first, findItemResult->second.second);
poseWidget->updateCheckedState(selected);
}
void PoseListWidget::selectPose(QUuid poseId, bool multiple)
{
if (multiple) {
if (!m_currentSelectedPoseId.isNull()) {
m_selectedPoseIds.insert(m_currentSelectedPoseId);
m_currentSelectedPoseId = QUuid();
}
if (m_selectedPoseIds.find(poseId) != m_selectedPoseIds.end()) {
updatePoseSelectState(poseId, false);
m_selectedPoseIds.erase(poseId);
} else {
updatePoseSelectState(poseId, true);
m_selectedPoseIds.insert(poseId);
}
if (m_selectedPoseIds.size() > 1) {
return;
}
if (m_selectedPoseIds.size() == 1)
poseId = *m_selectedPoseIds.begin();
else
poseId = QUuid();
}
if (!m_selectedPoseIds.empty()) {
for (const auto &id: m_selectedPoseIds) {
updatePoseSelectState(id, false);
}
m_selectedPoseIds.clear();
}
if (m_currentSelectedPoseId != poseId) {
if (!m_currentSelectedPoseId.isNull()) {
updatePoseSelectState(m_currentSelectedPoseId, false);
}
m_currentSelectedPoseId = poseId;
if (!m_currentSelectedPoseId.isNull()) {
updatePoseSelectState(m_currentSelectedPoseId, true);
}
}
}
void PoseListWidget::mousePressEvent(QMouseEvent *event)
{
QModelIndex itemIndex = indexAt(event->pos());
QTreeView::mousePressEvent(event);
if (event->button() == Qt::LeftButton) {
bool multiple = QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier);
if (itemIndex.isValid()) {
QTreeWidgetItem *item = itemFromIndex(itemIndex);
auto poseId = QUuid(item->data(itemIndex.column(), Qt::UserRole).toString());
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) {
bool startAdd = false;
bool stopAdd = false;
std::vector<QUuid> waitQueue;
for (const auto &childId: m_document->poseIdList) {
if (m_shiftStartPoseId == childId || poseId == childId) {
if (startAdd) {
stopAdd = true;
} else {
startAdd = true;
}
}
if (startAdd)
waitQueue.push_back(childId);
if (stopAdd)
break;
}
if (stopAdd && !waitQueue.empty()) {
if (!m_selectedPoseIds.empty()) {
for (const auto &id: m_selectedPoseIds) {
updatePoseSelectState(id, false);
}
m_selectedPoseIds.clear();
}
if (!m_currentSelectedPoseId.isNull()) {
m_currentSelectedPoseId = QUuid();
}
for (const auto &waitId: waitQueue) {
selectPose(waitId, true);
}
}
return;
} else {
m_shiftStartPoseId = poseId;
}
selectPose(poseId, multiple);
return;
}
if (!multiple)
selectPose(QUuid());
}
}
bool PoseListWidget::isPoseSelected(QUuid poseId)
{
return (m_currentSelectedPoseId == poseId ||
m_selectedPoseIds.find(poseId) != m_selectedPoseIds.end());
}
void PoseListWidget::showContextMenu(const QPoint &pos)
{
QMenu contextMenu(this);
std::set<QUuid> unorderedPoseIds = m_selectedPoseIds;
if (!m_currentSelectedPoseId.isNull())
unorderedPoseIds.insert(m_currentSelectedPoseId);
std::vector<QUuid> poseIds;
for (const auto &cand: m_document->poseIdList) {
if (unorderedPoseIds.find(cand) != unorderedPoseIds.end())
poseIds.push_back(cand);
}
QAction modifyAction(tr("Modify"), this);
if (poseIds.size() == 1) {
connect(&modifyAction, &QAction::triggered, this, [=]() {
emit modifyPose(*poseIds.begin());
});
contextMenu.addAction(&modifyAction);
}
QAction copyAction(tr("Copy"), this);
if (!poseIds.empty()) {
connect(&copyAction, &QAction::triggered, this, &PoseListWidget::copy);
contextMenu.addAction(&copyAction);
}
QAction pasteAction(tr("Paste"), this);
if (m_document->hasPastablePosesInClipboard()) {
connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
contextMenu.addAction(&pasteAction);
}
QAction deleteAction(tr("Delete"), this);
if (!poseIds.empty()) {
connect(&deleteAction, &QAction::triggered, [=]() {
for (const auto &poseId: poseIds)
emit removePose(poseId);
});
contextMenu.addAction(&deleteAction);
}
contextMenu.exec(mapToGlobal(pos));
}
void PoseListWidget::resizeEvent(QResizeEvent *event)
{
QTreeWidget::resizeEvent(event);
if (calculateColumnCount() != columnCount())
reload();
}
int PoseListWidget::calculateColumnCount()
{
if (nullptr == parentWidget())
return 0;
int columns = parentWidget()->width() / Theme::posePreviewImageSize;
if (0 == columns)
columns = 1;
return columns;
}
void PoseListWidget::reload()
{
removeAllContent();
int columns = calculateColumnCount();
if (0 == columns)
return;
int columnWidth = parentWidget()->width() / columns;
//qDebug() << "parentWidth:" << parentWidget()->width() << "columnWidth:" << columnWidth << "columns:" << columns;
setColumnCount(columns);
for (int i = 0; i < columns; i++)
setColumnWidth(i, columnWidth);
decltype(m_document->poseIdList.size()) poseIndex = 0;
while (poseIndex < m_document->poseIdList.size()) {
QTreeWidgetItem *item = new QTreeWidgetItem(this);
item->setFlags((item->flags() | Qt::ItemIsEditable | Qt::ItemIsEnabled) & ~(Qt::ItemIsSelectable));
for (int col = 0; col < columns && poseIndex < m_document->poseIdList.size(); col++, poseIndex++) {
const auto &poseId = m_document->poseIdList[poseIndex];
item->setSizeHint(col, QSize(columnWidth, PoseWidget::preferredHeight() + 2));
item->setData(col, Qt::UserRole, poseId.toString());
PoseWidget *widget = new PoseWidget(m_document, poseId);
connect(widget, &PoseWidget::modifyPose, this, &PoseListWidget::modifyPose);
widget->previewWidget()->setGraphicsFunctions(this);
setItemWidget(item, col, widget);
widget->reload();
widget->updateCheckedState(isPoseSelected(poseId));
m_itemMap[poseId] = std::make_pair(item, col);
}
invisibleRootItem()->addChild(item);
}
}
void PoseListWidget::removeAllContent()
{
m_itemMap.clear();
clear();
}
bool PoseListWidget::mouseMove(QMouseEvent *event)
{
return false;
}
bool PoseListWidget::wheel(QWheelEvent *event)
{
return false;
}
bool PoseListWidget::mouseRelease(QMouseEvent *event)
{
return false;
}
bool PoseListWidget::mousePress(QMouseEvent *event)
{
if (event->button() == Qt::RightButton) {
showContextMenu(mapFromGlobal(event->globalPos()));
return false;
}
return false;
}
bool PoseListWidget::mouseDoubleClick(QMouseEvent *event)
{
return false;
}
bool PoseListWidget::keyPress(QKeyEvent *event)
{
return false;
}
void PoseListWidget::copy()
{
if (m_selectedPoseIds.empty() && m_currentSelectedPoseId.isNull())
return;
std::set<QUuid> limitPoseIds = m_selectedPoseIds;
if (!m_currentSelectedPoseId.isNull())
limitPoseIds.insert(m_currentSelectedPoseId);
std::set<QUuid> emptySet;
SkeletonSnapshot snapshot;
m_document->toSnapshot(&snapshot, emptySet, SkeletonDocumentToSnapshotFor::Poses,
limitPoseIds);
QString snapshotXml;
QXmlStreamWriter xmlStreamWriter(&snapshotXml);
saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter);
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(snapshotXml);
}

44
src/poselistwidget.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef POSE_LIST_WIDGET_H
#define POSE_LIST_WIDGET_H
#include <QTreeWidget>
#include <map>
#include "skeletondocument.h"
#include "posewidget.h"
#include "skeletongraphicswidget.h"
class PoseListWidget : public QTreeWidget, public SkeletonGraphicsFunctions
{
Q_OBJECT
signals:
void removePose(QUuid poseId);
void modifyPose(QUuid poseId);
public:
PoseListWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
bool isPoseSelected(QUuid poseId);
public slots:
void reload();
void removeAllContent();
void poseRemoved(QUuid poseId);
void showContextMenu(const QPoint &pos);
void selectPose(QUuid poseId, bool multiple=false);
void copy();
protected:
void resizeEvent(QResizeEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
bool mouseMove(QMouseEvent *event);
bool wheel(QWheelEvent *event);
bool mouseRelease(QMouseEvent *event);
bool mousePress(QMouseEvent *event);
bool mouseDoubleClick(QMouseEvent *event);
bool keyPress(QKeyEvent *event);
private:
int calculateColumnCount();
void updatePoseSelectState(QUuid poseId, bool selected);
const SkeletonDocument *m_document = nullptr;
std::map<QUuid, std::pair<QTreeWidgetItem *, int>> m_itemMap;
std::set<QUuid> m_selectedPoseIds;
QUuid m_currentSelectedPoseId;
QUuid m_shiftStartPoseId;
};
#endif

62
src/posemanagewidget.cpp Normal file
View File

@ -0,0 +1,62 @@
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
#include "posemanagewidget.h"
#include "theme.h"
#include "poseeditwidget.h"
PoseManageWidget::PoseManageWidget(const SkeletonDocument *document, QWidget *parent) :
QWidget(parent),
m_document(document)
{
QPushButton *addPoseButton = new QPushButton(Theme::awesome()->icon(fa::plus), tr("Add Pose..."));
connect(addPoseButton, &QPushButton::clicked, this, &PoseManageWidget::showAddPoseDialog);
QHBoxLayout *toolsLayout = new QHBoxLayout;
toolsLayout->addWidget(addPoseButton);
m_poseListWidget = new PoseListWidget(document);
connect(m_poseListWidget, &PoseListWidget::modifyPose, this, &PoseManageWidget::showPoseDialog);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(toolsLayout);
mainLayout->addWidget(m_poseListWidget);
setLayout(mainLayout);
}
PoseListWidget *PoseManageWidget::poseListWidget()
{
return m_poseListWidget;
}
QSize PoseManageWidget::sizeHint() const
{
return QSize(Theme::sidebarPreferredWidth, 0);
}
void PoseManageWidget::showAddPoseDialog()
{
showPoseDialog(QUuid());
}
void PoseManageWidget::showPoseDialog(QUuid poseId)
{
PoseEditWidget *poseEditWidget = new PoseEditWidget(m_document);
poseEditWidget->setAttribute(Qt::WA_DeleteOnClose);
if (!poseId.isNull()) {
const SkeletonPose *pose = m_document->findPose(poseId);
if (nullptr != pose) {
poseEditWidget->setEditPoseId(poseId);
poseEditWidget->setEditPoseName(pose->name);
poseEditWidget->setEditParameters(pose->parameters);
poseEditWidget->clearUnsaveState();
}
}
poseEditWidget->show();
connect(poseEditWidget, &QDialog::destroyed, [=]() {
emit unregisterDialog((QWidget *)poseEditWidget);
});
emit registerDialog((QWidget *)poseEditWidget);
}

26
src/posemanagewidget.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef POSE_MANAGE_WIDGET_H
#define POSE_MANAGE_WIDGET_H
#include <QWidget>
#include "skeletondocument.h"
#include "poselistwidget.h"
class PoseManageWidget : public QWidget
{
Q_OBJECT
signals:
void registerDialog(QWidget *widget);
void unregisterDialog(QWidget *widget);
public:
PoseManageWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
PoseListWidget *poseListWidget();
protected:
virtual QSize sizeHint() const;
public slots:
void showAddPoseDialog();
void showPoseDialog(QUuid poseId);
private:
const SkeletonDocument *m_document = nullptr;
PoseListWidget *m_poseListWidget = nullptr;
};
#endif

View File

@ -49,12 +49,11 @@ void PosePreviewManager::poseMeshReady()
delete m_previewMesh;
m_previewMesh = m_poseMeshCreator->takeResultMesh();
emit resultPreviewMeshChanged();
qDebug() << "Pose mesh generation done";
delete m_poseMeshCreator;
m_poseMeshCreator = nullptr;
emit resultPreviewMeshChanged();
emit renderDone();
}

View File

@ -0,0 +1,66 @@
#include <QGuiApplication>
#include <QElapsedTimer>
#include "posepreviewsgenerator.h"
#include "tetrapodposer.h"
#include "posemeshcreator.h"
PosePreviewsGenerator::PosePreviewsGenerator(const std::vector<AutoRiggerBone> *rigBones,
const std::map<int, AutoRiggerVertexWeights> *rigWeights,
const MeshResultContext &meshResultContext) :
m_rigBones(*rigBones),
m_rigWeights(*rigWeights),
m_meshResultContext(new MeshResultContext(meshResultContext))
{
}
PosePreviewsGenerator::~PosePreviewsGenerator()
{
for (auto &item: m_previews) {
delete item.second;
}
delete m_meshResultContext;
}
void PosePreviewsGenerator::addPose(QUuid poseId, const std::map<QString, std::map<QString, QString>> &pose)
{
m_poses.push_back(std::make_pair(poseId, pose));
}
const std::set<QUuid> &PosePreviewsGenerator::generatedPreviewPoseIds()
{
return m_generatedPoseIds;
}
MeshLoader *PosePreviewsGenerator::takePreview(QUuid poseId)
{
MeshLoader *resultMesh = m_previews[poseId];
m_previews[poseId] = nullptr;
return resultMesh;
}
void PosePreviewsGenerator::process()
{
QElapsedTimer countTimeConsumed;
countTimeConsumed.start();
TetrapodPoser *poser = new TetrapodPoser(m_rigBones);
for (const auto &pose: m_poses) {
poser->parameters() = pose.second;
poser->commit();
PoseMeshCreator *poseMeshCreator = new PoseMeshCreator(*poser, *m_meshResultContext, m_rigWeights);
poseMeshCreator->createMesh();
m_previews[pose.first] = poseMeshCreator->takeResultMesh();
delete poseMeshCreator;
poser->reset();
m_generatedPoseIds.insert(pose.first);
}
delete poser;
qDebug() << "The pose previews generation took" << countTimeConsumed.elapsed() << "milliseconds";
this->moveToThread(QGuiApplication::instance()->thread());
emit finished();
}

View File

@ -0,0 +1,35 @@
#ifndef POSE_PREVIEWS_GENERATOR_H
#define POSE_PREVIEWS_GENERATOR_H
#include <QObject>
#include <map>
#include <QUuid>
#include <vector>
#include "meshloader.h"
#include "autorigger.h"
#include "meshresultcontext.h"
class PosePreviewsGenerator : public QObject
{
Q_OBJECT
public:
PosePreviewsGenerator(const std::vector<AutoRiggerBone> *rigBones,
const std::map<int, AutoRiggerVertexWeights> *rigWeights,
const MeshResultContext &meshResultContext);
~PosePreviewsGenerator();
void addPose(QUuid poseId, const std::map<QString, std::map<QString, QString>> &pose);
const std::set<QUuid> &generatedPreviewPoseIds();
MeshLoader *takePreview(QUuid poseId);
signals:
void finished();
public slots:
void process();
private:
std::vector<AutoRiggerBone> m_rigBones;
std::map<int, AutoRiggerVertexWeights> m_rigWeights;
MeshResultContext *m_meshResultContext = nullptr;
std::vector<std::pair<QUuid, std::map<QString, std::map<QString, QString>>>> m_poses;
std::map<QUuid, MeshLoader *> m_previews;
std::set<QUuid> m_generatedPoseIds;
};
#endif

View File

@ -16,7 +16,7 @@ public:
const std::vector<AutoRiggerBone> &bones() const;
const std::vector<JointNode> &resultNodes() const;
std::map<QString, std::map<QString, QString>> &parameters();
void commit();
virtual void commit();
void reset();
protected:
std::vector<AutoRiggerBone> m_bones;

93
src/posewidget.cpp Normal file
View File

@ -0,0 +1,93 @@
#include <QVBoxLayout>
#include "posewidget.h"
PoseWidget::PoseWidget(const SkeletonDocument *document, QUuid poseId) :
m_poseId(poseId),
m_document(document)
{
setObjectName("PoseFrame");
m_previewWidget = new ModelWidget(this);
m_previewWidget->setFixedSize(Theme::posePreviewImageSize, Theme::posePreviewImageSize);
m_previewWidget->enableMove(false);
m_previewWidget->enableZoom(false);
m_nameLabel = new QLabel;
m_nameLabel->setAlignment(Qt::AlignCenter);
m_nameLabel->setStyleSheet("background: qlineargradient(x1:0.5 y1:-15.5, x2:0.5 y2:1, stop:0 " + Theme::white.name() + ", stop:1 #252525);");
QFont nameFont;
nameFont.setWeight(QFont::Light);
nameFont.setPixelSize(9);
nameFont.setBold(false);
m_nameLabel->setFont(nameFont);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->addStretch();
mainLayout->addWidget(m_nameLabel);
setLayout(mainLayout);
setFixedSize(Theme::posePreviewImageSize, PoseWidget::preferredHeight());
connect(document, &SkeletonDocument::poseNameChanged, this, &PoseWidget::updateName);
connect(document, &SkeletonDocument::posePreviewChanged, this, &PoseWidget::updatePreview);
}
void PoseWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
m_previewWidget->move((width() - Theme::posePreviewImageSize) / 2, 0);
}
int PoseWidget::preferredHeight()
{
return Theme::posePreviewImageSize;
}
void PoseWidget::reload()
{
updatePreview();
updateName();
}
void PoseWidget::updatePreview()
{
const SkeletonPose *pose = m_document->findPose(m_poseId);
if (!pose) {
qDebug() << "Pose not found:" << m_poseId;
return;
}
MeshLoader *previewMesh = pose->takePreviewMesh();
m_previewWidget->updateMesh(previewMesh);
}
void PoseWidget::updateName()
{
const SkeletonPose *pose = m_document->findPose(m_poseId);
if (!pose) {
qDebug() << "Pose not found:" << m_poseId;
return;
}
m_nameLabel->setText(pose->name);
}
void PoseWidget::updateCheckedState(bool checked)
{
if (checked)
setStyleSheet("#PoseFrame {border: 1px solid " + Theme::red.name() + ";}");
else
setStyleSheet("#PoseFrame {border: 1px solid transparent;}");
}
ModelWidget *PoseWidget::previewWidget()
{
return m_previewWidget;
}
void PoseWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
QFrame::mouseDoubleClickEvent(event);
emit modifyPose(m_poseId);
}

32
src/posewidget.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef POSE_WIDGET_H
#define POSE_WIDGET_H
#include <QFrame>
#include <QLabel>
#include "skeletondocument.h"
#include "modelwidget.h"
class PoseWidget : public QFrame
{
Q_OBJECT
signals:
void modifyPose(QUuid poseId);
public:
PoseWidget(const SkeletonDocument *document, QUuid poseId);
static int preferredHeight();
ModelWidget *previewWidget();
protected:
void mouseDoubleClickEvent(QMouseEvent *event) override;
public slots:
void reload();
void updatePreview();
void updateName();
void updateCheckedState(bool checked);
void resizeEvent(QResizeEvent *event) override;
private:
QUuid m_poseId;
const SkeletonDocument *m_document = nullptr;
ModelWidget *m_previewWidget = nullptr;
QLabel *m_nameLabel = nullptr;
};
#endif

View File

@ -51,7 +51,8 @@ SkeletonDocument::SkeletonDocument() :
m_resultRigBones(nullptr),
m_resultRigWeights(nullptr),
m_isRigObsolete(false),
m_riggedResultContext(new MeshResultContext)
m_riggedResultContext(new MeshResultContext),
m_posePreviewsGenerator(nullptr)
{
}
@ -281,8 +282,6 @@ QUuid SkeletonDocument::createNode(float x, float y, float z, float radius, QUui
nodeMap[node.id] = node;
partMap[partId].nodeIds.push_back(node.id);
qDebug() << "Add node " << node.id << x << y << z;
emit nodeAdded(node.id);
if (nullptr != fromNode) {
@ -306,6 +305,66 @@ QUuid SkeletonDocument::createNode(float x, float y, float z, float radius, QUui
return node.id;
}
void SkeletonDocument::addPose(QString name, std::map<QString, std::map<QString, QString>> parameters)
{
QUuid newPoseId = QUuid::createUuid();
auto &pose = poseMap[newPoseId];
pose.id = newPoseId;
pose.name = name;
pose.parameters = parameters;
pose.dirty = true;
poseIdList.push_back(newPoseId);
emit poseAdded(newPoseId);
emit poseListChanged();
emit optionsChanged();
}
void SkeletonDocument::removePose(QUuid poseId)
{
auto findPoseResult = poseMap.find(poseId);
if (findPoseResult == poseMap.end()) {
qDebug() << "Remove a none exist pose:" << poseId;
return;
}
poseIdList.erase(std::remove(poseIdList.begin(), poseIdList.end(), poseId), poseIdList.end());
poseMap.erase(findPoseResult);
emit poseListChanged();
emit poseRemoved(poseId);
emit optionsChanged();
}
void SkeletonDocument::setPoseParameters(QUuid poseId, std::map<QString, std::map<QString, QString>> parameters)
{
auto findPoseResult = poseMap.find(poseId);
if (findPoseResult == poseMap.end()) {
qDebug() << "Find pose failed:" << poseId;
return;
}
findPoseResult->second.parameters = parameters;
findPoseResult->second.dirty = true;
emit poseParametersChanged(poseId);
emit optionsChanged();
}
void SkeletonDocument::renamePose(QUuid poseId, QString name)
{
auto findPoseResult = poseMap.find(poseId);
if (findPoseResult == poseMap.end()) {
qDebug() << "Find pose failed:" << poseId;
return;
}
if (findPoseResult->second.name == name)
return;
findPoseResult->second.name = name;
emit poseNameChanged(poseId);
emit optionsChanged();
}
const SkeletonEdge *SkeletonDocument::findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const
{
const SkeletonNode *firstNode = nullptr;
@ -446,6 +505,14 @@ const SkeletonComponent *SkeletonDocument::findComponent(QUuid componentId) cons
return &it->second;
}
const SkeletonPose *SkeletonDocument::findPose(QUuid poseId) const
{
auto it = poseMap.find(poseId);
if (it == poseMap.end())
return nullptr;
return &it->second;
}
void SkeletonDocument::scaleNodeByAddRadius(QUuid nodeId, float amount)
{
auto it = nodeMap.find(nodeId);
@ -675,8 +742,11 @@ void SkeletonDocument::markAllDirty()
}
}
void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds) const
void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds,
SkeletonDocumentToSnapshotFor forWhat, const std::set<QUuid> &limitPoseIds) const
{
if (SkeletonDocumentToSnapshotFor::Document == forWhat ||
SkeletonDocumentToSnapshotFor::Nodes == forWhat) {
std::set<QUuid> limitPartIds;
std::set<QUuid> limitComponentIds;
for (const auto &nodeId: limitNodeIds) {
@ -790,13 +860,33 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUu
if (!children.isEmpty())
snapshot->rootComponent["children"] = children;
}
}
if (SkeletonDocumentToSnapshotFor::Document == forWhat ||
SkeletonDocumentToSnapshotFor::Poses == forWhat) {
for (const auto &poseId: poseIdList) {
if (!limitPoseIds.empty() && limitPoseIds.find(poseId) == limitPoseIds.end())
continue;
auto findPoseResult = poseMap.find(poseId);
if (findPoseResult == poseMap.end()) {
qDebug() << "Find pose failed:" << poseId;
continue;
}
auto &poseIt = *findPoseResult;
std::map<QString, QString> pose;
pose["id"] = poseIt.second.id.toString();
if (!poseIt.second.name.isEmpty())
pose["name"] = poseIt.second.name;
snapshot->poses.push_back(std::make_pair(pose, poseIt.second.parameters));
}
}
if (SkeletonDocumentToSnapshotFor::Document == forWhat) {
std::map<QString, QString> canvas;
canvas["originX"] = QString::number(originX);
canvas["originY"] = QString::number(originY);
canvas["originZ"] = QString::number(originZ);
canvas["rigType"] = RigTypeToString(rigType);
snapshot->canvas = canvas;
}
}
void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste)
@ -921,11 +1011,11 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
const auto &smoothSeamIt = componentKv.second.find("smoothSeam");
if (smoothSeamIt != componentKv.second.end())
component.setSmoothSeam(smoothSeamIt->second.toFloat());
qDebug() << "Add component:" << component.id << " old:" << componentKv.first << "name:" << component.name;
//qDebug() << "Add component:" << component.id << " old:" << componentKv.first << "name:" << component.name;
if ("partId" == linkDataType) {
QUuid partId = oldNewIdMap[QUuid(linkData)];
component.linkToPartId = partId;
qDebug() << "Add part:" << partId << " from component:" << component.id;
//qDebug() << "Add part:" << partId << " from component:" << component.id;
partMap[partId].componentId = component.id;
if (inversePartIds.find(partId) != inversePartIds.end())
component.inverse = true;
@ -941,7 +1031,7 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
QUuid componentId = oldNewIdMap[QUuid(childId)];
if (componentMap.find(componentId) == componentMap.end())
continue;
qDebug() << "Add root component:" << componentId;
//qDebug() << "Add root component:" << componentId;
rootComponent.addChild(componentId);
}
}
@ -955,11 +1045,22 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
QUuid childComponentId = oldNewIdMap[QUuid(childId)];
if (componentMap.find(childComponentId) == componentMap.end())
continue;
qDebug() << "Add child component:" << childComponentId << "to" << componentId;
//qDebug() << "Add child component:" << childComponentId << "to" << componentId;
componentMap[componentId].addChild(childComponentId);
componentMap[childComponentId].parentId = componentId;
}
}
for (const auto &poseIt: snapshot.poses) {
QUuid newPoseId = QUuid::createUuid();
auto &newPose = poseMap[newPoseId];
newPose.id = newPoseId;
const auto &poseAttributes = poseIt.first;
newPose.name = valueOfKeyInMapOrEmpty(poseAttributes, "name");
newPose.parameters = poseIt.second;
oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(poseAttributes, "id"))] = newPoseId;
poseIdList.push_back(newPoseId);
emit poseAdded(newPoseId);
}
for (const auto &nodeIt: newAddedNodeIds) {
emit nodeAdded(nodeIt);
@ -989,6 +1090,9 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
for (const auto &edgeIt: newAddedEdgeIds) {
emit checkEdge(edgeIt);
}
if (!snapshot.poses.empty())
emit poseListChanged();
}
void SkeletonDocument::reset()
@ -1001,6 +1105,8 @@ void SkeletonDocument::reset()
edgeMap.clear();
partMap.clear();
componentMap.clear();
poseMap.clear();
poseIdList.clear();
rootComponent = SkeletonComponent();
emit cleanup();
emit skeletonChanged();
@ -1954,12 +2060,23 @@ void SkeletonDocument::paste()
}
}
bool SkeletonDocument::hasPastableContentInClipboard() const
bool SkeletonDocument::hasPastableNodesInClipboard() const
{
const QClipboard *clipboard = QApplication::clipboard();
const QMimeData *mimeData = clipboard->mimeData();
if (mimeData->hasText()) {
if (-1 != mimeData->text().left(1000).indexOf("partIdList"))
if (-1 != mimeData->text().left(1000).indexOf("<node "))
return true;
}
return false;
}
bool SkeletonDocument::hasPastablePosesInClipboard() const
{
const QClipboard *clipboard = QApplication::clipboard();
const QMimeData *mimeData = clipboard->mimeData();
if (mimeData->hasText()) {
if (-1 != mimeData->text().right(1000).indexOf("<pose "))
return true;
}
return false;
@ -2337,3 +2454,63 @@ const MeshResultContext &SkeletonDocument::currentRiggedResultContext() const
{
return *m_riggedResultContext;
}
void SkeletonDocument::generatePosePreviews()
{
if (nullptr != m_posePreviewsGenerator) {
return;
}
const std::vector<AutoRiggerBone> *rigBones = resultRigBones();
const std::map<int, AutoRiggerVertexWeights> *rigWeights = resultRigWeights();
if (nullptr == rigBones || nullptr == rigWeights) {
return;
}
QThread *thread = new QThread;
m_posePreviewsGenerator = new PosePreviewsGenerator(rigBones,
rigWeights, *m_riggedResultContext);
bool hasDirtyPose = false;
for (auto &poseIt: poseMap) {
if (!poseIt.second.dirty)
continue;
m_posePreviewsGenerator->addPose(poseIt.first, poseIt.second.parameters);
poseIt.second.dirty = false;
hasDirtyPose = true;
}
if (!hasDirtyPose) {
delete m_posePreviewsGenerator;
m_posePreviewsGenerator = nullptr;
delete thread;
return;
}
qDebug() << "Pose previews generating..";
m_posePreviewsGenerator->moveToThread(thread);
connect(thread, &QThread::started, m_posePreviewsGenerator, &PosePreviewsGenerator::process);
connect(m_posePreviewsGenerator, &PosePreviewsGenerator::finished, this, &SkeletonDocument::posePreviewsReady);
connect(m_posePreviewsGenerator, &PosePreviewsGenerator::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
void SkeletonDocument::posePreviewsReady()
{
for (const auto &poseId: m_posePreviewsGenerator->generatedPreviewPoseIds()) {
auto pose = poseMap.find(poseId);
if (pose != poseMap.end()) {
MeshLoader *resultPartPreviewMesh = m_posePreviewsGenerator->takePreview(poseId);
pose->second.updatePreviewMesh(resultPartPreviewMesh);
emit posePreviewChanged(poseId);
}
}
delete m_posePreviewsGenerator;
m_posePreviewsGenerator = nullptr;
qDebug() << "Pose previews generation done";
generatePosePreviews();
}

View File

@ -20,6 +20,7 @@
#include "skeletonbonemark.h"
#include "riggenerator.h"
#include "rigtype.h"
#include "posepreviewsgenerator.h"
class SkeletonNode
{
@ -345,6 +346,43 @@ private:
std::set<QUuid> m_childrenIdSet;
};
class SkeletonPose
{
public:
SkeletonPose()
{
}
~SkeletonPose()
{
delete m_previewMesh;
}
QUuid id;
QString name;
bool dirty = true;
std::map<QString, std::map<QString, QString>> parameters;
void updatePreviewMesh(MeshLoader *previewMesh)
{
delete m_previewMesh;
m_previewMesh = previewMesh;
}
MeshLoader *takePreviewMesh() const
{
if (nullptr == m_previewMesh)
return nullptr;
return new MeshLoader(*m_previewMesh);
}
private:
Q_DISABLE_COPY(SkeletonPose);
MeshLoader *m_previewMesh = nullptr;
};
enum class SkeletonDocumentToSnapshotFor
{
Document = 0,
Nodes,
Poses
};
class SkeletonDocument : public QObject
{
Q_OBJECT
@ -406,6 +444,12 @@ signals:
void checkEdge(QUuid edgeId);
void optionsChanged();
void rigTypeChanged();
void poseAdded(QUuid poseId);
void poseRemoved(QUuid);
void poseListChanged();
void poseNameChanged(QUuid poseId);
void poseParametersChanged(QUuid poseId);
void posePreviewChanged(QUuid poseId);
public: // need initialize
float originX;
float originY;
@ -428,10 +472,14 @@ public:
std::map<QUuid, SkeletonNode> nodeMap;
std::map<QUuid, SkeletonEdge> edgeMap;
std::map<QUuid, SkeletonComponent> componentMap;
std::map<QUuid, SkeletonPose> poseMap;
std::vector<QUuid> poseIdList;
SkeletonComponent rootComponent;
QImage turnaround;
QImage preview;
void toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>()) const;
void toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>(),
SkeletonDocumentToSnapshotFor forWhat=SkeletonDocumentToSnapshotFor::Document,
const std::set<QUuid> &limitPoseIds=std::set<QUuid>()) const;
void fromSnapshot(const SkeletonSnapshot &snapshot);
void addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste=true);
const SkeletonNode *findNode(QUuid nodeId) const;
@ -441,6 +489,7 @@ public:
const SkeletonComponent *findComponent(QUuid componentId) const;
const SkeletonComponent *findComponentParent(QUuid componentId) const;
QUuid findComponentParentId(QUuid componentId) const;
const SkeletonPose *findPose(QUuid poseId) const;
MeshLoader *takeResultMesh();
MeshLoader *takeResultTextureMesh();
MeshLoader *takeResultRigWeightMesh();
@ -448,7 +497,8 @@ public:
const std::map<int, AutoRiggerVertexWeights> *resultRigWeights() const;
void updateTurnaround(const QImage &image);
void setSharedContextWidget(QOpenGLWidget *sharedContextWidget);
bool hasPastableContentInClipboard() const;
bool hasPastableNodesInClipboard() const;
bool hasPastablePosesInClipboard() const;
bool undoable() const;
bool redoable() const;
bool isNodeEditable(QUuid nodeId) const;
@ -489,6 +539,8 @@ public slots:
void ambientOcclusionTextureReady();
void generateRig();
void rigReady();
void generatePosePreviews();
void posePreviewsReady();
void setPartLockState(QUuid partId, bool locked);
void setPartVisibleState(QUuid partId, bool visible);
void setPartSubdivState(QUuid partId, bool subdived);
@ -543,6 +595,10 @@ public slots:
void disableAllPositionRelatedLocks();
void toggleSmoothNormal();
void setRigType(RigType toRigType);
void addPose(QString name, std::map<QString, std::map<QString, QString>> parameters);
void removePose(QUuid poseId);
void setPoseParameters(QUuid poseId, std::map<QString, std::map<QString, QString>> parameters);
void renamePose(QUuid poseId, QString name);
private:
void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId);
void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid());
@ -583,6 +639,7 @@ private: // need initialize
std::map<int, AutoRiggerVertexWeights> *m_resultRigWeights;
bool m_isRigObsolete;
MeshResultContext *m_riggedResultContext;
PosePreviewsGenerator *m_posePreviewsGenerator;
private:
static unsigned long m_maxSnapshot;
std::deque<SkeletonHistoryItem> m_undoItems;

View File

@ -30,7 +30,6 @@
#include "skeletonparttreewidget.h"
#include "rigwidget.h"
#include "markiconcreator.h"
#include "tetrapodposeeditwidget.h"
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
@ -55,7 +54,7 @@ void SkeletonDocumentWindow::showAcknowlegements()
{
if (!g_acknowlegementsWidget) {
g_acknowlegementsWidget = new QTextBrowser;
g_acknowlegementsWidget->setWindowTitle(APP_NAME);
g_acknowlegementsWidget->setWindowTitle(unifiedWindowTitle(tr("Acknowlegements")));
g_acknowlegementsWidget->setMinimumSize(QSize(400, 300));
QFile file(":/ACKNOWLEDGEMENTS.html");
file.open(QFile::ReadOnly | QFile::Text);
@ -71,7 +70,7 @@ void SkeletonDocumentWindow::showContributors()
{
if (!g_contributorsWidget) {
g_contributorsWidget = new QTextBrowser;
g_contributorsWidget->setWindowTitle(APP_NAME);
g_contributorsWidget->setWindowTitle(unifiedWindowTitle(tr("Contributors")));
g_contributorsWidget->setMinimumSize(QSize(400, 300));
QFile authors(":/AUTHORS");
authors.open(QFile::ReadOnly | QFile::Text);
@ -225,16 +224,21 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
updateRigWeightRenderWidget();
});
//QDockWidget *animationDocker = new QDockWidget(tr("Animation"), this);
//animationDocker->setAllowedAreas(Qt::RightDockWidgetArea);
//TetrapodPoseEditWidget *tetrapodPoseEditWidget = new TetrapodPoseEditWidget(m_document, animationDocker);
//animationDocker->setWidget(tetrapodPoseEditWidget);
//AnimationListWidget *animationListWidget = new AnimationListWidget(m_document, animationDocker);
//animationDocker->setWidget(animationListWidget);
//addDockWidget(Qt::RightDockWidgetArea, animationDocker);
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);
});
tabifyDockWidget(partTreeDocker, rigDocker);
//tabifyDockWidget(rigDocker, animationDocker);
tabifyDockWidget(rigDocker, poseDocker);
partTreeDocker->raise();
@ -447,7 +451,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_connectAction->setEnabled(m_graphicsWidget->hasTwoDisconnectedNodesSelection());
m_cutAction->setEnabled(m_graphicsWidget->hasSelection());
m_copyAction->setEnabled(m_graphicsWidget->hasSelection());
m_pasteAction->setEnabled(m_document->hasPastableContentInClipboard());
m_pasteAction->setEnabled(m_document->hasPastableNodesInClipboard());
m_flipHorizontallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
m_flipVerticallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
m_rotateClockwiseAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
@ -512,12 +516,22 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
});
m_windowMenu->addAction(m_showRigAction);
//m_showAnimationAction = new QAction(tr("Animation"), this);
//connect(m_showAnimationAction, &QAction::triggered, [=]() {
// animationDocker->show();
// animationDocker->raise();
//});
//m_windowMenu->addAction(m_showAnimationAction);
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);
@ -765,6 +779,16 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
//connect(m_document, &SkeletonDocument::resultRigChanged, tetrapodPoseEditWidget, &TetrapodPoseEditWidget::updatePreview);
connect(m_document, &SkeletonDocument::poseAdded, this, [=](QUuid poseId) {
Q_UNUSED(poseId);
m_document->generatePosePreviews();
});
connect(m_document, &SkeletonDocument::poseParametersChanged, this, [=](QUuid poseId) {
Q_UNUSED(poseId);
m_document->generatePosePreviews();
});
connect(m_document, &SkeletonDocument::resultRigChanged, m_document, &SkeletonDocument::generatePosePreviews);
connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady);
QTimer *timer = new QTimer(this);
@ -1058,13 +1082,13 @@ void SkeletonDocumentWindow::showExportPreview()
{
if (nullptr == m_exportPreviewWidget) {
m_exportPreviewWidget = new ExportPreviewWidget(m_document, this);
m_exportPreviewWidget->setWindowFlags(Qt::Tool);
connect(m_exportPreviewWidget, &ExportPreviewWidget::regenerate, m_document, &SkeletonDocument::regenerateMesh);
connect(m_exportPreviewWidget, &ExportPreviewWidget::save, this, &SkeletonDocumentWindow::exportGltfResult);
connect(m_document, &SkeletonDocument::resultMeshChanged, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner);
connect(m_document, &SkeletonDocument::exportReady, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner);
connect(m_document, &SkeletonDocument::resultTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
connect(m_document, &SkeletonDocument::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
registerDialog(m_exportPreviewWidget);
}
if (m_document->isPostProcessResultObsolete()) {
m_document->postProcess();
@ -1145,3 +1169,13 @@ void SkeletonDocumentWindow::updateRigWeightRenderWidget()
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());
}

View File

@ -12,6 +12,7 @@
#include "exportpreviewwidget.h"
#include "rigwidget.h"
#include "skeletonbonemark.h"
#include "posemanagewidget.h"
class SkeletonGraphicsWidget;
@ -56,6 +57,8 @@ public slots:
void updateZlockButtonState();
void updateRadiusLockButtonState();
void updateRigWeightRenderWidget();
void registerDialog(QWidget *widget);
void unregisterDialog(QWidget *widget);
private:
void initLockButton(QPushButton *button);
void setCurrentFilename(const QString &filename);
@ -65,12 +68,14 @@ private:
bool m_firstShow;
bool m_documentSaved;
ExportPreviewWidget *m_exportPreviewWidget;
std::vector<QWidget *> m_dialogs;
private:
QString m_currentFilename;
ModelWidget *m_modelRenderWidget;
SkeletonGraphicsWidget *m_graphicsWidget;
RigWidget *m_rigWidget;
PoseManageWidget *m_poseManageWidget;
QMenu *m_fileMenu;
QAction *m_newWindowAction;
@ -129,7 +134,6 @@ private:
QAction *m_showPartsListAction;
QAction *m_showDebugDialogAction;
QAction *m_showRigAction;
QAction *m_showAnimationAction;
QMenu *m_helpMenu;
QAction *m_viewSourceAction;

View File

@ -158,7 +158,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
}
QAction pasteAction(tr("Paste"), this);
if (m_document->hasPastableContentInClipboard()) {
if (m_document->hasPastableNodesInClipboard()) {
connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
contextMenu.addAction(&pasteAction);
}
@ -1193,7 +1193,6 @@ bool SkeletonGraphicsWidget::mousePress(QMouseEvent *event)
m_lastAddedX = unifiedMainPos.x();
m_lastAddedY = unifiedMainPos.y();
m_lastAddedZ = unifiedSidePos.x();
qDebug() << "Emit add node " << m_lastAddedX << m_lastAddedY << m_lastAddedZ;
emit addNode(unifiedMainPos.x(), unifiedMainPos.y(), unifiedSidePos.x(), sceneRadiusToUnified(m_cursorNodeItem->radius()), nullptr == m_addFromNodeItem ? QUuid() : m_addFromNodeItem->id());
emit groupOperationAdded();
return true;
@ -2158,7 +2157,7 @@ void SkeletonGraphicsWidget::copy()
if (nodeIdSet.empty())
return;
SkeletonSnapshot snapshot;
m_document->toSnapshot(&snapshot, nodeIdSet);
m_document->toSnapshot(&snapshot, nodeIdSet, SkeletonDocumentToSnapshotFor::Nodes);
QString snapshotXml;
QXmlStreamWriter xmlStreamWriter(&snapshotXml);
saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter);

View File

@ -281,7 +281,7 @@ void SkeletonPartTreeWidget::showContextMenu(const QPoint &pos)
ModelWidget *previewWidget = new ModelWidget;
previewWidget->enableMove(false);
previewWidget->enableZoom(false);
previewWidget->setFixedSize(Theme::previewImageSize, Theme::previewImageSize);
previewWidget->setFixedSize(Theme::partPreviewImageSize, Theme::partPreviewImageSize);
previewWidget->setXRotation(partWidget->previewWidget()->xRot());
previewWidget->setYRotation(partWidget->previewWidget()->yRot());
previewWidget->setZRotation(partWidget->previewWidget()->zRot());
@ -289,7 +289,7 @@ void SkeletonPartTreeWidget::showContextMenu(const QPoint &pos)
layout->addWidget(previewWidget);
} else {
QLabel *previewLabel = new QLabel;
previewLabel->setFixedHeight(Theme::previewImageSize);
previewLabel->setFixedHeight(Theme::partPreviewImageSize);
previewLabel->setStyleSheet("QLabel {color: " + Theme::red.name() + "}");
if (nullptr != component) {
previewLabel->setText(component->name);

View File

@ -55,7 +55,7 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p
m_previewWidget = new ModelWidget;
m_previewWidget->enableMove(false);
m_previewWidget->enableZoom(false);
m_previewWidget->setFixedSize(Theme::previewImageSize, Theme::previewImageSize);
m_previewWidget->setFixedSize(Theme::partPreviewImageSize, Theme::partPreviewImageSize);
QWidget *hrLightWidget = new QWidget;
hrLightWidget->setFixedHeight(1);
@ -99,7 +99,7 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p
QWidget *backgroundWidget = new QWidget;
backgroundWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
backgroundWidget->setFixedSize(preferredSize().width(), Theme::previewImageSize);
backgroundWidget->setFixedSize(preferredSize().width(), Theme::partPreviewImageSize);
backgroundWidget->setObjectName("background");
m_backgroundWidget = backgroundWidget;
backgroundWidget->setLayout(previewAndToolsLayout);
@ -238,7 +238,7 @@ ModelWidget *SkeletonPartWidget::previewWidget()
QSize SkeletonPartWidget::preferredSize()
{
return QSize(Theme::miniIconSize + Theme::previewImageSize + Theme::miniIconSize * 4 + 5 + 2, Theme::previewImageSize + 6);
return QSize(Theme::miniIconSize + Theme::partPreviewImageSize + Theme::miniIconSize * 4 + 5 + 2, Theme::partPreviewImageSize + 6);
}
void SkeletonPartWidget::updateAllButtons()

View File

@ -47,7 +47,7 @@ public:
static QSize preferredSize();
ModelWidget *previewWidget();
protected:
void mouseDoubleClickEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event) override;
public slots:
void showDeformSettingPopup(const QPoint &pos);
void showColorSettingPopup(const QPoint &pos);

View File

@ -15,6 +15,7 @@ public:
std::map<QString, std::map<QString, QString>> parts;
std::map<QString, std::map<QString, QString>> components;
std::map<QString, QString> rootComponent;
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> poses; // std::pair<Pose attributes, Bone attributes>
public:
void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString());
};

View File

@ -91,6 +91,29 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write
writer->writeEndElement();
}
writer->writeStartElement("poses");
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>>::iterator poseIterator;
for (poseIterator = snapshot->poses.begin(); poseIterator != snapshot->poses.end(); poseIterator++) {
std::map<QString, QString>::iterator poseAttributeIterator;
writer->writeStartElement("pose");
for (poseAttributeIterator = poseIterator->first.begin(); poseAttributeIterator != poseIterator->first.end(); poseAttributeIterator++) {
writer->writeAttribute(poseAttributeIterator->first, poseAttributeIterator->second);
}
std::map<QString, std::map<QString, QString>>::iterator itemsIterator;
for (itemsIterator = poseIterator->second.begin(); itemsIterator != poseIterator->second.end(); itemsIterator++) {
std::map<QString, QString>::iterator parametersIterator;
writer->writeStartElement("parameter");
writer->writeAttribute("for", itemsIterator->first);
for (parametersIterator = itemsIterator->second.begin(); parametersIterator != itemsIterator->second.end();
parametersIterator++) {
writer->writeAttribute(parametersIterator->first, parametersIterator->second);
}
writer->writeEndElement();
}
writer->writeEndElement();
}
writer->writeEndElement();
writer->writeEndElement();
writer->writeEndDocument();
@ -100,10 +123,14 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
{
std::stack<QString> componentStack;
std::vector<QString> elementNameStack;
std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>> currentPose;
while (!reader.atEnd()) {
reader.readNext();
if (!reader.isStartElement() && !reader.isEndElement())
if (!reader.isStartElement() && !reader.isEndElement()) {
if (!reader.name().toString().isEmpty())
qDebug() << "Skip xml element:" << reader.name().toString() << " tokenType:" << reader.tokenType();
continue;
}
QString baseName = reader.name().toString();
if (reader.isStartElement())
elementNameStack.push_back(baseName);
@ -114,6 +141,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
QString fullName = nameItems.join(".");
if (reader.isEndElement())
elementNameStack.pop_back();
//qDebug() << (reader.isStartElement() ? "<" : ">") << "fullName:" << fullName << "baseName:" << baseName;
if (reader.isStartElement()) {
if (fullName == "canvas") {
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
@ -172,10 +200,29 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
if (!parentChildrenIds.isEmpty())
parentChildrenIds += ",";
parentChildrenIds += componentId;
} else if (fullName == "canvas.poses.pose") {
QString poseId = reader.attributes().value("id").toString();
if (poseId.isEmpty())
continue;
currentPose = decltype(currentPose)();
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
currentPose.first[attr.name().toString()] = attr.value().toString();
}
} else if (fullName == "canvas.poses.pose.parameter") {
QString forWhat = reader.attributes().value("for").toString();
if (forWhat.isEmpty())
continue;
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
if ("for" == attr.name().toString())
continue;
currentPose.second[forWhat][attr.name().toString()] = attr.value().toString();
}
}
} else if (reader.isEndElement()) {
if (fullName.startsWith("canvas.components.component")) {
componentStack.pop();
} else if (fullName == "canvas.poses.pose") {
snapshot->poses.push_back(currentPose);
}
}
}

View File

@ -1,33 +0,0 @@
#ifndef TETRAPOD_POSE_EDIT_WIDGET_H
#define TETRAPOD_POSE_EDIT_WIDGET_H
#include <QWidget>
#include <map>
#include <QPointF>
#include "posepreviewmanager.h"
#include "tetrapodposer.h"
#include "skeletondocument.h"
enum class PopupWidgetType
{
PitchYawRoll,
Intersection
};
class TetrapodPoseEditWidget : public QWidget
{
Q_OBJECT
public:
TetrapodPoseEditWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
~TetrapodPoseEditWidget();
public slots:
void updatePreview();
void showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos);
private:
const SkeletonDocument *m_document = nullptr;
PosePreviewManager *m_posePreviewManager = nullptr;
TetrapodPoser *m_poser = nullptr;
bool m_isPreviewDirty = false;
std::map<QString, std::map<QString, QString>> m_parameters;
};
#endif

View File

@ -33,8 +33,9 @@ int Theme::toolIconFontSize = 16;
int Theme::toolIconSize = 24;
int Theme::miniIconFontSize = 9;
int Theme::miniIconSize = 15;
int Theme::previewImageSize = (Theme::miniIconSize * 3);
int Theme::previewImageRenderSize = 75;
int Theme::partPreviewImageSize = (Theme::miniIconSize * 3);
int Theme::posePreviewImageSize = 75;
int Theme::sidebarPreferredWidth = 200;
QtAwesome *Theme::awesome()
{
@ -128,3 +129,22 @@ void Theme::initAwesomeToolButton(QPushButton *button)
Theme::initAwesomeToolButtonWithoutFont(button);
}
QWidget *Theme::createHorizontalLineWidget()
{
QWidget *hrLightWidget = new QWidget;
hrLightWidget->setFixedHeight(1);
hrLightWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
hrLightWidget->setStyleSheet(QString("background-color: #565656;"));
hrLightWidget->setContentsMargins(0, 0, 0, 0);
return hrLightWidget;
}
QWidget *Theme::createVerticalLineWidget()
{
QWidget *hrLightWidget = new QWidget;
hrLightWidget->setFixedWidth(1);
hrLightWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
hrLightWidget->setStyleSheet(QString("background-color: #565656;"));
hrLightWidget->setContentsMargins(0, 0, 0, 0);
return hrLightWidget;
}

View File

@ -30,12 +30,15 @@ public:
static std::map<QString, QString> nextSideColorNameMap;
static std::map<QString, QColor> sideColorNameToColorMap;
static QtAwesome *awesome();
static QWidget *createHorizontalLineWidget();
static QWidget *createVerticalLineWidget();
static int toolIconFontSize;
static int toolIconSize;
static int previewImageRenderSize;
static int previewImageSize;
static int posePreviewImageSize;
static int partPreviewImageSize;
static int miniIconFontSize;
static int miniIconSize;
static int sidebarPreferredWidth;
public:
static void initAwesomeButton(QPushButton *button);
static void initAwesomeLabel(QLabel *label);