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
parent
f163522aa2
commit
e22a6de6cb
16
dust3d.pro
16
dust3d.pro
|
@ -179,8 +179,20 @@ HEADERS += src/posemeshcreator.h
|
||||||
SOURCES += src/posepreviewmanager.cpp
|
SOURCES += src/posepreviewmanager.cpp
|
||||||
HEADERS += src/posepreviewmanager.h
|
HEADERS += src/posepreviewmanager.h
|
||||||
|
|
||||||
SOURCES += src/tetrapodposeeditwidget.cpp
|
SOURCES += src/poseeditwidget.cpp
|
||||||
HEADERS += src/tetrapodposeeditwidget.h
|
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
|
SOURCES += src/main.cpp
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <QOpenGLFunctions>
|
#include <QOpenGLFunctions>
|
||||||
#include "aboutwidget.h"
|
#include "aboutwidget.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
#include "dust3dutil.h"
|
||||||
|
|
||||||
AboutWidget::AboutWidget()
|
AboutWidget::AboutWidget()
|
||||||
{
|
{
|
||||||
|
@ -16,5 +17,5 @@ AboutWidget::AboutWidget()
|
||||||
setLayout(mainLayout);
|
setLayout(mainLayout);
|
||||||
setFixedSize(QSize(350, 75));
|
setFixedSize(QSize(350, 75));
|
||||||
|
|
||||||
setWindowTitle(APP_NAME);
|
setWindowTitle(unifiedWindowTitle(tr("About")));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#ifndef ABOUT_WIDGET_H
|
#ifndef ABOUT_WIDGET_H
|
||||||
#define ABOUT_WIDGET_H
|
#define ABOUT_WIDGET_H
|
||||||
#include <QWidget>
|
#include <QDialog>
|
||||||
|
|
||||||
class AboutWidget : public QWidget
|
class AboutWidget : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include "dust3dutil.h"
|
#include "dust3dutil.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
QString valueOfKeyInMapOrEmpty(const std::map<QString, QString> &map, const QString &key)
|
QString valueOfKeyInMapOrEmpty(const std::map<QString, QString> &map, const QString &key)
|
||||||
{
|
{
|
||||||
|
@ -51,3 +52,7 @@ QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal)
|
||||||
return line - verticalOffset;
|
return line - verticalOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString unifiedWindowTitle(const QString &text)
|
||||||
|
{
|
||||||
|
return text + QObject::tr(" - ") + APP_NAME;
|
||||||
|
}
|
||||||
|
|
|
@ -17,5 +17,6 @@ void qNormalizeAngle(int &angle);
|
||||||
QVector3D pointInHermiteCurve(float t, QVector3D p0, QVector3D m0, QVector3D p1, QVector3D m1);
|
QVector3D pointInHermiteCurve(float t, QVector3D p0, QVector3D m0, QVector3D p1, QVector3D m1);
|
||||||
float angleInRangle360BetweenTwoVectors(QVector3D a, QVector3D b, QVector3D planeNormal);
|
float angleInRangle360BetweenTwoVectors(QVector3D a, QVector3D b, QVector3D planeNormal);
|
||||||
QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal);
|
QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal);
|
||||||
|
QString unifiedWindowTitle(const QString &text);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
#include "aboutwidget.h"
|
#include "aboutwidget.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
|
#include "dust3dutil.h"
|
||||||
|
|
||||||
ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *parent) :
|
ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *parent) :
|
||||||
QWidget(parent),
|
QDialog(parent),
|
||||||
m_document(document),
|
m_document(document),
|
||||||
m_previewLabel(nullptr),
|
m_previewLabel(nullptr),
|
||||||
m_spinnerWidget(nullptr)
|
m_spinnerWidget(nullptr)
|
||||||
|
@ -33,6 +34,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
|
||||||
m_saveButton = new QPushButton(tr("Save"));
|
m_saveButton = new QPushButton(tr("Save"));
|
||||||
connect(m_saveButton, &QPushButton::clicked, this, &ExportPreviewWidget::save);
|
connect(m_saveButton, &QPushButton::clicked, this, &ExportPreviewWidget::save);
|
||||||
m_saveButton->hide();
|
m_saveButton->hide();
|
||||||
|
m_saveButton->setDefault(true);
|
||||||
|
|
||||||
QComboBox *exportFormatSelectBox = new QComboBox;
|
QComboBox *exportFormatSelectBox = new QComboBox;
|
||||||
exportFormatSelectBox->addItem(tr("glTF"));
|
exportFormatSelectBox->addItem(tr("glTF"));
|
||||||
|
@ -59,11 +61,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
|
||||||
renderLayout->setContentsMargins(20, 0, 20, 0);
|
renderLayout->setContentsMargins(20, 0, 20, 0);
|
||||||
renderLayout->addWidget(m_textureRenderWidget);
|
renderLayout->addWidget(m_textureRenderWidget);
|
||||||
|
|
||||||
QWidget *hrLightWidget = new QWidget;
|
QWidget *hrLightWidget = Theme::createHorizontalLineWidget();
|
||||||
hrLightWidget->setFixedHeight(1);
|
|
||||||
hrLightWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
||||||
hrLightWidget->setStyleSheet(QString("background-color: #565656;"));
|
|
||||||
hrLightWidget->setContentsMargins(0, 0, 0, 0);
|
|
||||||
|
|
||||||
QHBoxLayout *topLayout = new QHBoxLayout;
|
QHBoxLayout *topLayout = new QHBoxLayout;
|
||||||
topLayout->setSpacing(0);
|
topLayout->setSpacing(0);
|
||||||
|
@ -86,7 +84,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
|
||||||
m_spinnerWidget->setNumberOfLines(12);
|
m_spinnerWidget->setNumberOfLines(12);
|
||||||
m_spinnerWidget->hide();
|
m_spinnerWidget->hide();
|
||||||
|
|
||||||
setWindowTitle(tr("Export") + tr(" - ") + APP_NAME);
|
setWindowTitle(unifiedWindowTitle(tr("Export")));
|
||||||
|
|
||||||
emit updateTexturePreview();
|
emit updateTexturePreview();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#ifndef EXPORT_PREVIEW_WIDGET_H
|
#ifndef EXPORT_PREVIEW_WIDGET_H
|
||||||
#define EXPORT_PREVIEW_WIDGET_H
|
#define EXPORT_PREVIEW_WIDGET_H
|
||||||
#include <QWidget>
|
#include <QDialog>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
#include "waitingspinnerwidget.h"
|
#include "waitingspinnerwidget.h"
|
||||||
#include "skeletondocument.h"
|
#include "skeletondocument.h"
|
||||||
|
|
||||||
class ExportPreviewWidget : public QWidget
|
class ExportPreviewWidget : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
signals:
|
signals:
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <QCloseEvent>
|
#include <QCloseEvent>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
#include "dust3dutil.h"
|
||||||
|
|
||||||
LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
|
LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
|
||||||
QDialog(parent)
|
QDialog(parent)
|
||||||
|
@ -40,7 +41,7 @@ LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
|
||||||
|
|
||||||
resize(400, 300);
|
resize(400, 300);
|
||||||
|
|
||||||
setWindowTitle(tr("Debug") + tr(" - ") + APP_NAME);
|
setWindowTitle(unifiedWindowTitle(tr("Debug")));
|
||||||
|
|
||||||
hide();
|
hide();
|
||||||
}
|
}
|
||||||
|
|
|
@ -434,7 +434,7 @@ void *MeshGenerator::combineComponentMesh(QString componentId, bool *inverse)
|
||||||
auto findCachedMesh = m_cacheContext->componentCombinableMeshs.find(componentId);
|
auto findCachedMesh = m_cacheContext->componentCombinableMeshs.find(componentId);
|
||||||
if (findCachedMesh != m_cacheContext->componentCombinableMeshs.end() &&
|
if (findCachedMesh != m_cacheContext->componentCombinableMeshs.end() &&
|
||||||
nullptr != findCachedMesh->second) {
|
nullptr != findCachedMesh->second) {
|
||||||
qDebug() << "Component mesh cache used:" << componentId;
|
//qDebug() << "Component mesh cache used:" << componentId;
|
||||||
return cloneCombinableMesh(findCachedMesh->second);
|
return cloneCombinableMesh(findCachedMesh->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -519,7 +519,7 @@ void *MeshGenerator::combineComponentMesh(QString componentId, bool *inverse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (seamVerticesNum > 0) {
|
if (seamVerticesNum > 0) {
|
||||||
qDebug() << "smoothSeamFactor:" << smoothSeamFactor << "seamVerticesIndicies.size():" << seamVerticesNum;
|
//qDebug() << "smoothSeamFactor:" << smoothSeamFactor << "seamVerticesIndicies.size():" << seamVerticesNum;
|
||||||
meshlite_smooth_vertices(m_meshliteContext, meshIdForSmooth, smoothSeamFactor, seamVerticesIndicies, seamVerticesNum);
|
meshlite_smooth_vertices(m_meshliteContext, meshIdForSmooth, smoothSeamFactor, seamVerticesIndicies, seamVerticesNum);
|
||||||
}
|
}
|
||||||
delete[] seamVerticesIndicies;
|
delete[] seamVerticesIndicies;
|
||||||
|
|
|
@ -1,21 +1,32 @@
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
#include <QFormLayout>
|
#include <QFormLayout>
|
||||||
#include <QGridLayout>
|
#include <QGridLayout>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QWidgetAction>
|
#include <QWidgetAction>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QMessageBox>
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
#include "tetrapodposeeditwidget.h"
|
#include "poseeditwidget.h"
|
||||||
#include "floatnumberwidget.h"
|
#include "floatnumberwidget.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
TetrapodPoseEditWidget::TetrapodPoseEditWidget(const SkeletonDocument *document, QWidget *parent) :
|
PoseEditWidget::PoseEditWidget(const SkeletonDocument *document, QWidget *parent) :
|
||||||
QWidget(parent),
|
QDialog(parent),
|
||||||
m_document(document)
|
m_document(document)
|
||||||
{
|
{
|
||||||
m_posePreviewManager = new PosePreviewManager();
|
m_posePreviewManager = new PosePreviewManager();
|
||||||
connect(m_posePreviewManager, &PosePreviewManager::renderDone, [=]() {
|
connect(m_posePreviewManager, &PosePreviewManager::renderDone, [=]() {
|
||||||
|
if (m_closed) {
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (m_isPreviewDirty)
|
if (m_isPreviewDirty)
|
||||||
updatePreview();
|
updatePreview();
|
||||||
});
|
});
|
||||||
|
connect(m_posePreviewManager, &PosePreviewManager::resultPreviewMeshChanged, [=]() {
|
||||||
|
m_previewWidget->updateMesh(m_posePreviewManager->takeResultPreviewMesh());
|
||||||
|
});
|
||||||
|
|
||||||
std::map<QString, std::tuple<QPushButton *, PopupWidgetType>> buttons;
|
std::map<QString, std::tuple<QPushButton *, PopupWidgetType>> buttons;
|
||||||
buttons["Head"] = std::make_tuple(new QPushButton(tr("Head")), PopupWidgetType::PitchYawRoll);
|
buttons["Head"] = std::make_tuple(new QPushButton(tr("Head")), PopupWidgetType::PitchYawRoll);
|
||||||
|
@ -61,36 +72,117 @@ TetrapodPoseEditWidget::TetrapodPoseEditWidget(const SkeletonDocument *document,
|
||||||
|
|
||||||
QFont buttonFont;
|
QFont buttonFont;
|
||||||
buttonFont.setWeight(QFont::Light);
|
buttonFont.setWeight(QFont::Light);
|
||||||
buttonFont.setPixelSize(7);
|
buttonFont.setPixelSize(9);
|
||||||
buttonFont.setBold(false);
|
buttonFont.setBold(false);
|
||||||
for (const auto &item: buttons) {
|
for (const auto &item: buttons) {
|
||||||
QString boneName = item.first;
|
QString boneName = item.first;
|
||||||
QPushButton *buttonWidget = std::get<0>(item.second);
|
QPushButton *buttonWidget = std::get<0>(item.second);
|
||||||
PopupWidgetType widgetType = std::get<1>(item.second);
|
PopupWidgetType widgetType = std::get<1>(item.second);
|
||||||
buttonWidget->setFont(buttonFont);
|
buttonWidget->setFont(buttonFont);
|
||||||
buttonWidget->setMaximumWidth(45);
|
buttonWidget->setMaximumWidth(55);
|
||||||
connect(buttonWidget, &QPushButton::clicked, [this, boneName, widgetType]() {
|
connect(buttonWidget, &QPushButton::clicked, [this, boneName, widgetType]() {
|
||||||
emit showPopupAngleDialog(boneName, widgetType, mapFromGlobal(QCursor::pos()));
|
emit showPopupAngleDialog(boneName, widgetType, mapFromGlobal(QCursor::pos()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QVBoxLayout *layout = new QVBoxLayout;
|
m_previewWidget = new ModelWidget(this);
|
||||||
layout->addLayout(marksContainerLayout);
|
m_previewWidget->setMinimumSize(128, 128);
|
||||||
layout->addLayout(lowerMarksContainerLayout);
|
m_previewWidget->resize(384, 384);
|
||||||
layout->addStretch();
|
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;
|
delete m_posePreviewManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TetrapodPoseEditWidget::updatePreview()
|
void PoseEditWidget::updatePreview()
|
||||||
{
|
{
|
||||||
|
if (m_closed)
|
||||||
|
return;
|
||||||
|
|
||||||
if (m_posePreviewManager->isRendering()) {
|
if (m_posePreviewManager->isRendering()) {
|
||||||
m_isPreviewDirty = true;
|
m_isPreviewDirty = true;
|
||||||
return;
|
return;
|
||||||
|
@ -105,14 +197,37 @@ void TetrapodPoseEditWidget::updatePreview()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete m_poser;
|
TetrapodPoser *poser = new TetrapodPoser(*rigBones);
|
||||||
m_poser = new TetrapodPoser(*rigBones);
|
poser->parameters() = m_parameters;
|
||||||
m_poser->parameters() = m_parameters;
|
poser->commit();
|
||||||
m_poser->commit();
|
m_posePreviewManager->postUpdate(*poser, m_document->currentRiggedResultContext(), *rigWeights);
|
||||||
m_posePreviewManager->postUpdate(*m_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;
|
QMenu popupMenu;
|
||||||
|
|
||||||
|
@ -125,13 +240,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
|
||||||
pitchWidget->setItemName(tr("Pitch"));
|
pitchWidget->setItemName(tr("Pitch"));
|
||||||
pitchWidget->setRange(-180, 180);
|
pitchWidget->setRange(-180, 180);
|
||||||
pitchWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "pitch").toFloat());
|
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);
|
m_parameters[boneName]["pitch"] = QString::number(value);
|
||||||
updatePreview();
|
emit parametersAdjusted();
|
||||||
});
|
});
|
||||||
QPushButton *pitchEraser = new QPushButton(QChar(fa::eraser));
|
QPushButton *pitchEraser = new QPushButton(QChar(fa::eraser));
|
||||||
Theme::initAwesomeMiniButton(pitchEraser);
|
Theme::initAwesomeMiniButton(pitchEraser);
|
||||||
connect(pitchEraser, &QPushButton::clicked, [=]() {
|
connect(pitchEraser, &QPushButton::clicked, this, [=]() {
|
||||||
pitchWidget->setValue(0.0);
|
pitchWidget->setValue(0.0);
|
||||||
});
|
});
|
||||||
QHBoxLayout *pitchLayout = new QHBoxLayout;
|
QHBoxLayout *pitchLayout = new QHBoxLayout;
|
||||||
|
@ -143,13 +258,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
|
||||||
yawWidget->setItemName(tr("Yaw"));
|
yawWidget->setItemName(tr("Yaw"));
|
||||||
yawWidget->setRange(-180, 180);
|
yawWidget->setRange(-180, 180);
|
||||||
yawWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "yaw").toFloat());
|
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);
|
m_parameters[boneName]["yaw"] = QString::number(value);
|
||||||
updatePreview();
|
emit parametersAdjusted();
|
||||||
});
|
});
|
||||||
QPushButton *yawEraser = new QPushButton(QChar(fa::eraser));
|
QPushButton *yawEraser = new QPushButton(QChar(fa::eraser));
|
||||||
Theme::initAwesomeMiniButton(yawEraser);
|
Theme::initAwesomeMiniButton(yawEraser);
|
||||||
connect(yawEraser, &QPushButton::clicked, [=]() {
|
connect(yawEraser, &QPushButton::clicked, this, [=]() {
|
||||||
yawWidget->setValue(0.0);
|
yawWidget->setValue(0.0);
|
||||||
});
|
});
|
||||||
QHBoxLayout *yawLayout = new QHBoxLayout;
|
QHBoxLayout *yawLayout = new QHBoxLayout;
|
||||||
|
@ -161,13 +276,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
|
||||||
rollWidget->setItemName(tr("Roll"));
|
rollWidget->setItemName(tr("Roll"));
|
||||||
rollWidget->setRange(-180, 180);
|
rollWidget->setRange(-180, 180);
|
||||||
rollWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "roll").toFloat());
|
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);
|
m_parameters[boneName]["roll"] = QString::number(value);
|
||||||
updatePreview();
|
emit parametersAdjusted();
|
||||||
});
|
});
|
||||||
QPushButton *rollEraser = new QPushButton(QChar(fa::eraser));
|
QPushButton *rollEraser = new QPushButton(QChar(fa::eraser));
|
||||||
Theme::initAwesomeMiniButton(rollEraser);
|
Theme::initAwesomeMiniButton(rollEraser);
|
||||||
connect(rollEraser, &QPushButton::clicked, [=]() {
|
connect(rollEraser, &QPushButton::clicked, this, [=]() {
|
||||||
rollWidget->setValue(0.0);
|
rollWidget->setValue(0.0);
|
||||||
});
|
});
|
||||||
QHBoxLayout *rollLayout = new QHBoxLayout;
|
QHBoxLayout *rollLayout = new QHBoxLayout;
|
||||||
|
@ -179,13 +294,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
|
||||||
intersectionWidget->setItemName(tr("Intersection"));
|
intersectionWidget->setItemName(tr("Intersection"));
|
||||||
intersectionWidget->setRange(-180, 180);
|
intersectionWidget->setRange(-180, 180);
|
||||||
intersectionWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "intersection").toFloat());
|
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);
|
m_parameters[boneName]["intersection"] = QString::number(value);
|
||||||
updatePreview();
|
emit parametersAdjusted();
|
||||||
});
|
});
|
||||||
QPushButton *intersectionEraser = new QPushButton(QChar(fa::eraser));
|
QPushButton *intersectionEraser = new QPushButton(QChar(fa::eraser));
|
||||||
Theme::initAwesomeMiniButton(intersectionEraser);
|
Theme::initAwesomeMiniButton(intersectionEraser);
|
||||||
connect(intersectionEraser, &QPushButton::clicked, [=]() {
|
connect(intersectionEraser, &QPushButton::clicked, this, [=]() {
|
||||||
intersectionWidget->setValue(0.0);
|
intersectionWidget->setValue(0.0);
|
||||||
});
|
});
|
||||||
QHBoxLayout *intersectionLayout = new QHBoxLayout;
|
QHBoxLayout *intersectionLayout = new QHBoxLayout;
|
||||||
|
@ -201,5 +316,40 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
|
||||||
|
|
||||||
popupMenu.addAction(&action);
|
popupMenu.addAction(&action);
|
||||||
|
|
||||||
|
m_openedMenuCount++;
|
||||||
popupMenu.exec(mapToGlobal(pos));
|
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();
|
||||||
}
|
}
|
|
@ -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
|
|
@ -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(©Action, &QAction::triggered, this, &PoseListWidget::copy);
|
||||||
|
contextMenu.addAction(©Action);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -49,12 +49,11 @@ void PosePreviewManager::poseMeshReady()
|
||||||
delete m_previewMesh;
|
delete m_previewMesh;
|
||||||
m_previewMesh = m_poseMeshCreator->takeResultMesh();
|
m_previewMesh = m_poseMeshCreator->takeResultMesh();
|
||||||
|
|
||||||
emit resultPreviewMeshChanged();
|
|
||||||
|
|
||||||
qDebug() << "Pose mesh generation done";
|
qDebug() << "Pose mesh generation done";
|
||||||
|
|
||||||
delete m_poseMeshCreator;
|
delete m_poseMeshCreator;
|
||||||
m_poseMeshCreator = nullptr;
|
m_poseMeshCreator = nullptr;
|
||||||
|
|
||||||
|
emit resultPreviewMeshChanged();
|
||||||
emit renderDone();
|
emit renderDone();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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
|
|
@ -16,7 +16,7 @@ public:
|
||||||
const std::vector<AutoRiggerBone> &bones() const;
|
const std::vector<AutoRiggerBone> &bones() const;
|
||||||
const std::vector<JointNode> &resultNodes() const;
|
const std::vector<JointNode> &resultNodes() const;
|
||||||
std::map<QString, std::map<QString, QString>> ¶meters();
|
std::map<QString, std::map<QString, QString>> ¶meters();
|
||||||
void commit();
|
virtual void commit();
|
||||||
void reset();
|
void reset();
|
||||||
protected:
|
protected:
|
||||||
std::vector<AutoRiggerBone> m_bones;
|
std::vector<AutoRiggerBone> m_bones;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -51,7 +51,8 @@ SkeletonDocument::SkeletonDocument() :
|
||||||
m_resultRigBones(nullptr),
|
m_resultRigBones(nullptr),
|
||||||
m_resultRigWeights(nullptr),
|
m_resultRigWeights(nullptr),
|
||||||
m_isRigObsolete(false),
|
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;
|
nodeMap[node.id] = node;
|
||||||
partMap[partId].nodeIds.push_back(node.id);
|
partMap[partId].nodeIds.push_back(node.id);
|
||||||
|
|
||||||
qDebug() << "Add node " << node.id << x << y << z;
|
|
||||||
|
|
||||||
emit nodeAdded(node.id);
|
emit nodeAdded(node.id);
|
||||||
|
|
||||||
if (nullptr != fromNode) {
|
if (nullptr != fromNode) {
|
||||||
|
@ -306,6 +305,66 @@ QUuid SkeletonDocument::createNode(float x, float y, float z, float radius, QUui
|
||||||
return node.id;
|
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 SkeletonEdge *SkeletonDocument::findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const
|
||||||
{
|
{
|
||||||
const SkeletonNode *firstNode = nullptr;
|
const SkeletonNode *firstNode = nullptr;
|
||||||
|
@ -446,6 +505,14 @@ const SkeletonComponent *SkeletonDocument::findComponent(QUuid componentId) cons
|
||||||
return &it->second;
|
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)
|
void SkeletonDocument::scaleNodeByAddRadius(QUuid nodeId, float amount)
|
||||||
{
|
{
|
||||||
auto it = nodeMap.find(nodeId);
|
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> limitPartIds;
|
||||||
std::set<QUuid> limitComponentIds;
|
std::set<QUuid> limitComponentIds;
|
||||||
for (const auto &nodeId: limitNodeIds) {
|
for (const auto &nodeId: limitNodeIds) {
|
||||||
|
@ -790,7 +860,26 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUu
|
||||||
if (!children.isEmpty())
|
if (!children.isEmpty())
|
||||||
snapshot->rootComponent["children"] = children;
|
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;
|
std::map<QString, QString> canvas;
|
||||||
canvas["originX"] = QString::number(originX);
|
canvas["originX"] = QString::number(originX);
|
||||||
canvas["originY"] = QString::number(originY);
|
canvas["originY"] = QString::number(originY);
|
||||||
|
@ -798,6 +887,7 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUu
|
||||||
canvas["rigType"] = RigTypeToString(rigType);
|
canvas["rigType"] = RigTypeToString(rigType);
|
||||||
snapshot->canvas = canvas;
|
snapshot->canvas = canvas;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste)
|
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");
|
const auto &smoothSeamIt = componentKv.second.find("smoothSeam");
|
||||||
if (smoothSeamIt != componentKv.second.end())
|
if (smoothSeamIt != componentKv.second.end())
|
||||||
component.setSmoothSeam(smoothSeamIt->second.toFloat());
|
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) {
|
if ("partId" == linkDataType) {
|
||||||
QUuid partId = oldNewIdMap[QUuid(linkData)];
|
QUuid partId = oldNewIdMap[QUuid(linkData)];
|
||||||
component.linkToPartId = partId;
|
component.linkToPartId = partId;
|
||||||
qDebug() << "Add part:" << partId << " from component:" << component.id;
|
//qDebug() << "Add part:" << partId << " from component:" << component.id;
|
||||||
partMap[partId].componentId = component.id;
|
partMap[partId].componentId = component.id;
|
||||||
if (inversePartIds.find(partId) != inversePartIds.end())
|
if (inversePartIds.find(partId) != inversePartIds.end())
|
||||||
component.inverse = true;
|
component.inverse = true;
|
||||||
|
@ -941,7 +1031,7 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
|
||||||
QUuid componentId = oldNewIdMap[QUuid(childId)];
|
QUuid componentId = oldNewIdMap[QUuid(childId)];
|
||||||
if (componentMap.find(componentId) == componentMap.end())
|
if (componentMap.find(componentId) == componentMap.end())
|
||||||
continue;
|
continue;
|
||||||
qDebug() << "Add root component:" << componentId;
|
//qDebug() << "Add root component:" << componentId;
|
||||||
rootComponent.addChild(componentId);
|
rootComponent.addChild(componentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -955,11 +1045,22 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
|
||||||
QUuid childComponentId = oldNewIdMap[QUuid(childId)];
|
QUuid childComponentId = oldNewIdMap[QUuid(childId)];
|
||||||
if (componentMap.find(childComponentId) == componentMap.end())
|
if (componentMap.find(childComponentId) == componentMap.end())
|
||||||
continue;
|
continue;
|
||||||
qDebug() << "Add child component:" << childComponentId << "to" << componentId;
|
//qDebug() << "Add child component:" << childComponentId << "to" << componentId;
|
||||||
componentMap[componentId].addChild(childComponentId);
|
componentMap[componentId].addChild(childComponentId);
|
||||||
componentMap[childComponentId].parentId = componentId;
|
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) {
|
for (const auto &nodeIt: newAddedNodeIds) {
|
||||||
emit nodeAdded(nodeIt);
|
emit nodeAdded(nodeIt);
|
||||||
|
@ -989,6 +1090,9 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
|
||||||
for (const auto &edgeIt: newAddedEdgeIds) {
|
for (const auto &edgeIt: newAddedEdgeIds) {
|
||||||
emit checkEdge(edgeIt);
|
emit checkEdge(edgeIt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!snapshot.poses.empty())
|
||||||
|
emit poseListChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonDocument::reset()
|
void SkeletonDocument::reset()
|
||||||
|
@ -1001,6 +1105,8 @@ void SkeletonDocument::reset()
|
||||||
edgeMap.clear();
|
edgeMap.clear();
|
||||||
partMap.clear();
|
partMap.clear();
|
||||||
componentMap.clear();
|
componentMap.clear();
|
||||||
|
poseMap.clear();
|
||||||
|
poseIdList.clear();
|
||||||
rootComponent = SkeletonComponent();
|
rootComponent = SkeletonComponent();
|
||||||
emit cleanup();
|
emit cleanup();
|
||||||
emit skeletonChanged();
|
emit skeletonChanged();
|
||||||
|
@ -1954,12 +2060,23 @@ void SkeletonDocument::paste()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SkeletonDocument::hasPastableContentInClipboard() const
|
bool SkeletonDocument::hasPastableNodesInClipboard() const
|
||||||
{
|
{
|
||||||
const QClipboard *clipboard = QApplication::clipboard();
|
const QClipboard *clipboard = QApplication::clipboard();
|
||||||
const QMimeData *mimeData = clipboard->mimeData();
|
const QMimeData *mimeData = clipboard->mimeData();
|
||||||
if (mimeData->hasText()) {
|
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 true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -2337,3 +2454,63 @@ const MeshResultContext &SkeletonDocument::currentRiggedResultContext() const
|
||||||
{
|
{
|
||||||
return *m_riggedResultContext;
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "skeletonbonemark.h"
|
#include "skeletonbonemark.h"
|
||||||
#include "riggenerator.h"
|
#include "riggenerator.h"
|
||||||
#include "rigtype.h"
|
#include "rigtype.h"
|
||||||
|
#include "posepreviewsgenerator.h"
|
||||||
|
|
||||||
class SkeletonNode
|
class SkeletonNode
|
||||||
{
|
{
|
||||||
|
@ -345,6 +346,43 @@ private:
|
||||||
std::set<QUuid> m_childrenIdSet;
|
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
|
class SkeletonDocument : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -406,6 +444,12 @@ signals:
|
||||||
void checkEdge(QUuid edgeId);
|
void checkEdge(QUuid edgeId);
|
||||||
void optionsChanged();
|
void optionsChanged();
|
||||||
void rigTypeChanged();
|
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
|
public: // need initialize
|
||||||
float originX;
|
float originX;
|
||||||
float originY;
|
float originY;
|
||||||
|
@ -428,10 +472,14 @@ public:
|
||||||
std::map<QUuid, SkeletonNode> nodeMap;
|
std::map<QUuid, SkeletonNode> nodeMap;
|
||||||
std::map<QUuid, SkeletonEdge> edgeMap;
|
std::map<QUuid, SkeletonEdge> edgeMap;
|
||||||
std::map<QUuid, SkeletonComponent> componentMap;
|
std::map<QUuid, SkeletonComponent> componentMap;
|
||||||
|
std::map<QUuid, SkeletonPose> poseMap;
|
||||||
|
std::vector<QUuid> poseIdList;
|
||||||
SkeletonComponent rootComponent;
|
SkeletonComponent rootComponent;
|
||||||
QImage turnaround;
|
QImage turnaround;
|
||||||
QImage preview;
|
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 fromSnapshot(const SkeletonSnapshot &snapshot);
|
||||||
void addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste=true);
|
void addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste=true);
|
||||||
const SkeletonNode *findNode(QUuid nodeId) const;
|
const SkeletonNode *findNode(QUuid nodeId) const;
|
||||||
|
@ -441,6 +489,7 @@ public:
|
||||||
const SkeletonComponent *findComponent(QUuid componentId) const;
|
const SkeletonComponent *findComponent(QUuid componentId) const;
|
||||||
const SkeletonComponent *findComponentParent(QUuid componentId) const;
|
const SkeletonComponent *findComponentParent(QUuid componentId) const;
|
||||||
QUuid findComponentParentId(QUuid componentId) const;
|
QUuid findComponentParentId(QUuid componentId) const;
|
||||||
|
const SkeletonPose *findPose(QUuid poseId) const;
|
||||||
MeshLoader *takeResultMesh();
|
MeshLoader *takeResultMesh();
|
||||||
MeshLoader *takeResultTextureMesh();
|
MeshLoader *takeResultTextureMesh();
|
||||||
MeshLoader *takeResultRigWeightMesh();
|
MeshLoader *takeResultRigWeightMesh();
|
||||||
|
@ -448,7 +497,8 @@ public:
|
||||||
const std::map<int, AutoRiggerVertexWeights> *resultRigWeights() const;
|
const std::map<int, AutoRiggerVertexWeights> *resultRigWeights() const;
|
||||||
void updateTurnaround(const QImage &image);
|
void updateTurnaround(const QImage &image);
|
||||||
void setSharedContextWidget(QOpenGLWidget *sharedContextWidget);
|
void setSharedContextWidget(QOpenGLWidget *sharedContextWidget);
|
||||||
bool hasPastableContentInClipboard() const;
|
bool hasPastableNodesInClipboard() const;
|
||||||
|
bool hasPastablePosesInClipboard() const;
|
||||||
bool undoable() const;
|
bool undoable() const;
|
||||||
bool redoable() const;
|
bool redoable() const;
|
||||||
bool isNodeEditable(QUuid nodeId) const;
|
bool isNodeEditable(QUuid nodeId) const;
|
||||||
|
@ -489,6 +539,8 @@ public slots:
|
||||||
void ambientOcclusionTextureReady();
|
void ambientOcclusionTextureReady();
|
||||||
void generateRig();
|
void generateRig();
|
||||||
void rigReady();
|
void rigReady();
|
||||||
|
void generatePosePreviews();
|
||||||
|
void posePreviewsReady();
|
||||||
void setPartLockState(QUuid partId, bool locked);
|
void setPartLockState(QUuid partId, bool locked);
|
||||||
void setPartVisibleState(QUuid partId, bool visible);
|
void setPartVisibleState(QUuid partId, bool visible);
|
||||||
void setPartSubdivState(QUuid partId, bool subdived);
|
void setPartSubdivState(QUuid partId, bool subdived);
|
||||||
|
@ -543,6 +595,10 @@ public slots:
|
||||||
void disableAllPositionRelatedLocks();
|
void disableAllPositionRelatedLocks();
|
||||||
void toggleSmoothNormal();
|
void toggleSmoothNormal();
|
||||||
void setRigType(RigType toRigType);
|
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:
|
private:
|
||||||
void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId);
|
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());
|
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;
|
std::map<int, AutoRiggerVertexWeights> *m_resultRigWeights;
|
||||||
bool m_isRigObsolete;
|
bool m_isRigObsolete;
|
||||||
MeshResultContext *m_riggedResultContext;
|
MeshResultContext *m_riggedResultContext;
|
||||||
|
PosePreviewsGenerator *m_posePreviewsGenerator;
|
||||||
private:
|
private:
|
||||||
static unsigned long m_maxSnapshot;
|
static unsigned long m_maxSnapshot;
|
||||||
std::deque<SkeletonHistoryItem> m_undoItems;
|
std::deque<SkeletonHistoryItem> m_undoItems;
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
#include "skeletonparttreewidget.h"
|
#include "skeletonparttreewidget.h"
|
||||||
#include "rigwidget.h"
|
#include "rigwidget.h"
|
||||||
#include "markiconcreator.h"
|
#include "markiconcreator.h"
|
||||||
#include "tetrapodposeeditwidget.h"
|
|
||||||
|
|
||||||
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
|
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
|
||||||
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
|
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
|
||||||
|
@ -55,7 +54,7 @@ void SkeletonDocumentWindow::showAcknowlegements()
|
||||||
{
|
{
|
||||||
if (!g_acknowlegementsWidget) {
|
if (!g_acknowlegementsWidget) {
|
||||||
g_acknowlegementsWidget = new QTextBrowser;
|
g_acknowlegementsWidget = new QTextBrowser;
|
||||||
g_acknowlegementsWidget->setWindowTitle(APP_NAME);
|
g_acknowlegementsWidget->setWindowTitle(unifiedWindowTitle(tr("Acknowlegements")));
|
||||||
g_acknowlegementsWidget->setMinimumSize(QSize(400, 300));
|
g_acknowlegementsWidget->setMinimumSize(QSize(400, 300));
|
||||||
QFile file(":/ACKNOWLEDGEMENTS.html");
|
QFile file(":/ACKNOWLEDGEMENTS.html");
|
||||||
file.open(QFile::ReadOnly | QFile::Text);
|
file.open(QFile::ReadOnly | QFile::Text);
|
||||||
|
@ -71,7 +70,7 @@ void SkeletonDocumentWindow::showContributors()
|
||||||
{
|
{
|
||||||
if (!g_contributorsWidget) {
|
if (!g_contributorsWidget) {
|
||||||
g_contributorsWidget = new QTextBrowser;
|
g_contributorsWidget = new QTextBrowser;
|
||||||
g_contributorsWidget->setWindowTitle(APP_NAME);
|
g_contributorsWidget->setWindowTitle(unifiedWindowTitle(tr("Contributors")));
|
||||||
g_contributorsWidget->setMinimumSize(QSize(400, 300));
|
g_contributorsWidget->setMinimumSize(QSize(400, 300));
|
||||||
QFile authors(":/AUTHORS");
|
QFile authors(":/AUTHORS");
|
||||||
authors.open(QFile::ReadOnly | QFile::Text);
|
authors.open(QFile::ReadOnly | QFile::Text);
|
||||||
|
@ -225,16 +224,21 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
updateRigWeightRenderWidget();
|
updateRigWeightRenderWidget();
|
||||||
});
|
});
|
||||||
|
|
||||||
//QDockWidget *animationDocker = new QDockWidget(tr("Animation"), this);
|
QDockWidget *poseDocker = new QDockWidget(tr("Poses"), this);
|
||||||
//animationDocker->setAllowedAreas(Qt::RightDockWidgetArea);
|
poseDocker->setAllowedAreas(Qt::RightDockWidgetArea);
|
||||||
//TetrapodPoseEditWidget *tetrapodPoseEditWidget = new TetrapodPoseEditWidget(m_document, animationDocker);
|
PoseManageWidget *poseManageWidget = new PoseManageWidget(m_document, poseDocker);
|
||||||
//animationDocker->setWidget(tetrapodPoseEditWidget);
|
poseDocker->setWidget(poseManageWidget);
|
||||||
//AnimationListWidget *animationListWidget = new AnimationListWidget(m_document, animationDocker);
|
connect(poseManageWidget, &PoseManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog);
|
||||||
//animationDocker->setWidget(animationListWidget);
|
connect(poseManageWidget, &PoseManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog);
|
||||||
//addDockWidget(Qt::RightDockWidgetArea, animationDocker);
|
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(partTreeDocker, rigDocker);
|
||||||
//tabifyDockWidget(rigDocker, animationDocker);
|
tabifyDockWidget(rigDocker, poseDocker);
|
||||||
|
|
||||||
partTreeDocker->raise();
|
partTreeDocker->raise();
|
||||||
|
|
||||||
|
@ -447,7 +451,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
m_connectAction->setEnabled(m_graphicsWidget->hasTwoDisconnectedNodesSelection());
|
m_connectAction->setEnabled(m_graphicsWidget->hasTwoDisconnectedNodesSelection());
|
||||||
m_cutAction->setEnabled(m_graphicsWidget->hasSelection());
|
m_cutAction->setEnabled(m_graphicsWidget->hasSelection());
|
||||||
m_copyAction->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_flipHorizontallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
|
||||||
m_flipVerticallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
|
m_flipVerticallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
|
||||||
m_rotateClockwiseAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
|
m_rotateClockwiseAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
|
||||||
|
@ -512,12 +516,22 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
});
|
});
|
||||||
m_windowMenu->addAction(m_showRigAction);
|
m_windowMenu->addAction(m_showRigAction);
|
||||||
|
|
||||||
//m_showAnimationAction = new QAction(tr("Animation"), this);
|
QMenu *dialogsMenu = m_windowMenu->addMenu(tr("Dialogs"));
|
||||||
//connect(m_showAnimationAction, &QAction::triggered, [=]() {
|
connect(dialogsMenu, &QMenu::aboutToShow, [=]() {
|
||||||
// animationDocker->show();
|
dialogsMenu->clear();
|
||||||
// animationDocker->raise();
|
if (this->m_dialogs.empty()) {
|
||||||
//});
|
QAction *action = dialogsMenu->addAction(tr("None"));
|
||||||
//m_windowMenu->addAction(m_showAnimationAction);
|
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);
|
m_showDebugDialogAction = new QAction(tr("Debug"), this);
|
||||||
connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog);
|
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::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);
|
connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady);
|
||||||
|
|
||||||
QTimer *timer = new QTimer(this);
|
QTimer *timer = new QTimer(this);
|
||||||
|
@ -1058,13 +1082,13 @@ void SkeletonDocumentWindow::showExportPreview()
|
||||||
{
|
{
|
||||||
if (nullptr == m_exportPreviewWidget) {
|
if (nullptr == m_exportPreviewWidget) {
|
||||||
m_exportPreviewWidget = new ExportPreviewWidget(m_document, this);
|
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::regenerate, m_document, &SkeletonDocument::regenerateMesh);
|
||||||
connect(m_exportPreviewWidget, &ExportPreviewWidget::save, this, &SkeletonDocumentWindow::exportGltfResult);
|
connect(m_exportPreviewWidget, &ExportPreviewWidget::save, this, &SkeletonDocumentWindow::exportGltfResult);
|
||||||
connect(m_document, &SkeletonDocument::resultMeshChanged, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner);
|
connect(m_document, &SkeletonDocument::resultMeshChanged, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner);
|
||||||
connect(m_document, &SkeletonDocument::exportReady, 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::resultTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
|
||||||
connect(m_document, &SkeletonDocument::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
|
connect(m_document, &SkeletonDocument::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
|
||||||
|
registerDialog(m_exportPreviewWidget);
|
||||||
}
|
}
|
||||||
if (m_document->isPostProcessResultObsolete()) {
|
if (m_document->isPostProcessResultObsolete()) {
|
||||||
m_document->postProcess();
|
m_document->postProcess();
|
||||||
|
@ -1145,3 +1169,13 @@ void SkeletonDocumentWindow::updateRigWeightRenderWidget()
|
||||||
m_rigWidget->rigWeightRenderWidget()->update();
|
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());
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "exportpreviewwidget.h"
|
#include "exportpreviewwidget.h"
|
||||||
#include "rigwidget.h"
|
#include "rigwidget.h"
|
||||||
#include "skeletonbonemark.h"
|
#include "skeletonbonemark.h"
|
||||||
|
#include "posemanagewidget.h"
|
||||||
|
|
||||||
class SkeletonGraphicsWidget;
|
class SkeletonGraphicsWidget;
|
||||||
|
|
||||||
|
@ -56,6 +57,8 @@ public slots:
|
||||||
void updateZlockButtonState();
|
void updateZlockButtonState();
|
||||||
void updateRadiusLockButtonState();
|
void updateRadiusLockButtonState();
|
||||||
void updateRigWeightRenderWidget();
|
void updateRigWeightRenderWidget();
|
||||||
|
void registerDialog(QWidget *widget);
|
||||||
|
void unregisterDialog(QWidget *widget);
|
||||||
private:
|
private:
|
||||||
void initLockButton(QPushButton *button);
|
void initLockButton(QPushButton *button);
|
||||||
void setCurrentFilename(const QString &filename);
|
void setCurrentFilename(const QString &filename);
|
||||||
|
@ -65,12 +68,14 @@ private:
|
||||||
bool m_firstShow;
|
bool m_firstShow;
|
||||||
bool m_documentSaved;
|
bool m_documentSaved;
|
||||||
ExportPreviewWidget *m_exportPreviewWidget;
|
ExportPreviewWidget *m_exportPreviewWidget;
|
||||||
|
std::vector<QWidget *> m_dialogs;
|
||||||
private:
|
private:
|
||||||
QString m_currentFilename;
|
QString m_currentFilename;
|
||||||
|
|
||||||
ModelWidget *m_modelRenderWidget;
|
ModelWidget *m_modelRenderWidget;
|
||||||
SkeletonGraphicsWidget *m_graphicsWidget;
|
SkeletonGraphicsWidget *m_graphicsWidget;
|
||||||
RigWidget *m_rigWidget;
|
RigWidget *m_rigWidget;
|
||||||
|
PoseManageWidget *m_poseManageWidget;
|
||||||
|
|
||||||
QMenu *m_fileMenu;
|
QMenu *m_fileMenu;
|
||||||
QAction *m_newWindowAction;
|
QAction *m_newWindowAction;
|
||||||
|
@ -129,7 +134,6 @@ private:
|
||||||
QAction *m_showPartsListAction;
|
QAction *m_showPartsListAction;
|
||||||
QAction *m_showDebugDialogAction;
|
QAction *m_showDebugDialogAction;
|
||||||
QAction *m_showRigAction;
|
QAction *m_showRigAction;
|
||||||
QAction *m_showAnimationAction;
|
|
||||||
|
|
||||||
QMenu *m_helpMenu;
|
QMenu *m_helpMenu;
|
||||||
QAction *m_viewSourceAction;
|
QAction *m_viewSourceAction;
|
||||||
|
|
|
@ -158,7 +158,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
QAction pasteAction(tr("Paste"), this);
|
QAction pasteAction(tr("Paste"), this);
|
||||||
if (m_document->hasPastableContentInClipboard()) {
|
if (m_document->hasPastableNodesInClipboard()) {
|
||||||
connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
|
connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
|
||||||
contextMenu.addAction(&pasteAction);
|
contextMenu.addAction(&pasteAction);
|
||||||
}
|
}
|
||||||
|
@ -1193,7 +1193,6 @@ bool SkeletonGraphicsWidget::mousePress(QMouseEvent *event)
|
||||||
m_lastAddedX = unifiedMainPos.x();
|
m_lastAddedX = unifiedMainPos.x();
|
||||||
m_lastAddedY = unifiedMainPos.y();
|
m_lastAddedY = unifiedMainPos.y();
|
||||||
m_lastAddedZ = unifiedSidePos.x();
|
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 addNode(unifiedMainPos.x(), unifiedMainPos.y(), unifiedSidePos.x(), sceneRadiusToUnified(m_cursorNodeItem->radius()), nullptr == m_addFromNodeItem ? QUuid() : m_addFromNodeItem->id());
|
||||||
emit groupOperationAdded();
|
emit groupOperationAdded();
|
||||||
return true;
|
return true;
|
||||||
|
@ -2158,7 +2157,7 @@ void SkeletonGraphicsWidget::copy()
|
||||||
if (nodeIdSet.empty())
|
if (nodeIdSet.empty())
|
||||||
return;
|
return;
|
||||||
SkeletonSnapshot snapshot;
|
SkeletonSnapshot snapshot;
|
||||||
m_document->toSnapshot(&snapshot, nodeIdSet);
|
m_document->toSnapshot(&snapshot, nodeIdSet, SkeletonDocumentToSnapshotFor::Nodes);
|
||||||
QString snapshotXml;
|
QString snapshotXml;
|
||||||
QXmlStreamWriter xmlStreamWriter(&snapshotXml);
|
QXmlStreamWriter xmlStreamWriter(&snapshotXml);
|
||||||
saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter);
|
saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter);
|
||||||
|
|
|
@ -281,7 +281,7 @@ void SkeletonPartTreeWidget::showContextMenu(const QPoint &pos)
|
||||||
ModelWidget *previewWidget = new ModelWidget;
|
ModelWidget *previewWidget = new ModelWidget;
|
||||||
previewWidget->enableMove(false);
|
previewWidget->enableMove(false);
|
||||||
previewWidget->enableZoom(false);
|
previewWidget->enableZoom(false);
|
||||||
previewWidget->setFixedSize(Theme::previewImageSize, Theme::previewImageSize);
|
previewWidget->setFixedSize(Theme::partPreviewImageSize, Theme::partPreviewImageSize);
|
||||||
previewWidget->setXRotation(partWidget->previewWidget()->xRot());
|
previewWidget->setXRotation(partWidget->previewWidget()->xRot());
|
||||||
previewWidget->setYRotation(partWidget->previewWidget()->yRot());
|
previewWidget->setYRotation(partWidget->previewWidget()->yRot());
|
||||||
previewWidget->setZRotation(partWidget->previewWidget()->zRot());
|
previewWidget->setZRotation(partWidget->previewWidget()->zRot());
|
||||||
|
@ -289,7 +289,7 @@ void SkeletonPartTreeWidget::showContextMenu(const QPoint &pos)
|
||||||
layout->addWidget(previewWidget);
|
layout->addWidget(previewWidget);
|
||||||
} else {
|
} else {
|
||||||
QLabel *previewLabel = new QLabel;
|
QLabel *previewLabel = new QLabel;
|
||||||
previewLabel->setFixedHeight(Theme::previewImageSize);
|
previewLabel->setFixedHeight(Theme::partPreviewImageSize);
|
||||||
previewLabel->setStyleSheet("QLabel {color: " + Theme::red.name() + "}");
|
previewLabel->setStyleSheet("QLabel {color: " + Theme::red.name() + "}");
|
||||||
if (nullptr != component) {
|
if (nullptr != component) {
|
||||||
previewLabel->setText(component->name);
|
previewLabel->setText(component->name);
|
||||||
|
|
|
@ -55,7 +55,7 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p
|
||||||
m_previewWidget = new ModelWidget;
|
m_previewWidget = new ModelWidget;
|
||||||
m_previewWidget->enableMove(false);
|
m_previewWidget->enableMove(false);
|
||||||
m_previewWidget->enableZoom(false);
|
m_previewWidget->enableZoom(false);
|
||||||
m_previewWidget->setFixedSize(Theme::previewImageSize, Theme::previewImageSize);
|
m_previewWidget->setFixedSize(Theme::partPreviewImageSize, Theme::partPreviewImageSize);
|
||||||
|
|
||||||
QWidget *hrLightWidget = new QWidget;
|
QWidget *hrLightWidget = new QWidget;
|
||||||
hrLightWidget->setFixedHeight(1);
|
hrLightWidget->setFixedHeight(1);
|
||||||
|
@ -99,7 +99,7 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p
|
||||||
|
|
||||||
QWidget *backgroundWidget = new QWidget;
|
QWidget *backgroundWidget = new QWidget;
|
||||||
backgroundWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
backgroundWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||||
backgroundWidget->setFixedSize(preferredSize().width(), Theme::previewImageSize);
|
backgroundWidget->setFixedSize(preferredSize().width(), Theme::partPreviewImageSize);
|
||||||
backgroundWidget->setObjectName("background");
|
backgroundWidget->setObjectName("background");
|
||||||
m_backgroundWidget = backgroundWidget;
|
m_backgroundWidget = backgroundWidget;
|
||||||
backgroundWidget->setLayout(previewAndToolsLayout);
|
backgroundWidget->setLayout(previewAndToolsLayout);
|
||||||
|
@ -238,7 +238,7 @@ ModelWidget *SkeletonPartWidget::previewWidget()
|
||||||
|
|
||||||
QSize SkeletonPartWidget::preferredSize()
|
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()
|
void SkeletonPartWidget::updateAllButtons()
|
||||||
|
|
|
@ -47,7 +47,7 @@ public:
|
||||||
static QSize preferredSize();
|
static QSize preferredSize();
|
||||||
ModelWidget *previewWidget();
|
ModelWidget *previewWidget();
|
||||||
protected:
|
protected:
|
||||||
void mouseDoubleClickEvent(QMouseEvent *event);
|
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||||
public slots:
|
public slots:
|
||||||
void showDeformSettingPopup(const QPoint &pos);
|
void showDeformSettingPopup(const QPoint &pos);
|
||||||
void showColorSettingPopup(const QPoint &pos);
|
void showColorSettingPopup(const QPoint &pos);
|
||||||
|
|
|
@ -15,6 +15,7 @@ public:
|
||||||
std::map<QString, std::map<QString, QString>> parts;
|
std::map<QString, std::map<QString, QString>> parts;
|
||||||
std::map<QString, std::map<QString, QString>> components;
|
std::map<QString, std::map<QString, QString>> components;
|
||||||
std::map<QString, QString> rootComponent;
|
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:
|
public:
|
||||||
void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString());
|
void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString());
|
||||||
};
|
};
|
||||||
|
|
|
@ -91,6 +91,29 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write
|
||||||
writer->writeEndElement();
|
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->writeEndElement();
|
||||||
|
|
||||||
writer->writeEndDocument();
|
writer->writeEndDocument();
|
||||||
|
@ -100,10 +123,14 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
|
||||||
{
|
{
|
||||||
std::stack<QString> componentStack;
|
std::stack<QString> componentStack;
|
||||||
std::vector<QString> elementNameStack;
|
std::vector<QString> elementNameStack;
|
||||||
|
std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>> currentPose;
|
||||||
while (!reader.atEnd()) {
|
while (!reader.atEnd()) {
|
||||||
reader.readNext();
|
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;
|
continue;
|
||||||
|
}
|
||||||
QString baseName = reader.name().toString();
|
QString baseName = reader.name().toString();
|
||||||
if (reader.isStartElement())
|
if (reader.isStartElement())
|
||||||
elementNameStack.push_back(baseName);
|
elementNameStack.push_back(baseName);
|
||||||
|
@ -114,6 +141,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
|
||||||
QString fullName = nameItems.join(".");
|
QString fullName = nameItems.join(".");
|
||||||
if (reader.isEndElement())
|
if (reader.isEndElement())
|
||||||
elementNameStack.pop_back();
|
elementNameStack.pop_back();
|
||||||
|
//qDebug() << (reader.isStartElement() ? "<" : ">") << "fullName:" << fullName << "baseName:" << baseName;
|
||||||
if (reader.isStartElement()) {
|
if (reader.isStartElement()) {
|
||||||
if (fullName == "canvas") {
|
if (fullName == "canvas") {
|
||||||
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
|
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
|
||||||
|
@ -172,10 +200,29 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
|
||||||
if (!parentChildrenIds.isEmpty())
|
if (!parentChildrenIds.isEmpty())
|
||||||
parentChildrenIds += ",";
|
parentChildrenIds += ",";
|
||||||
parentChildrenIds += componentId;
|
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()) {
|
} else if (reader.isEndElement()) {
|
||||||
if (fullName.startsWith("canvas.components.component")) {
|
if (fullName.startsWith("canvas.components.component")) {
|
||||||
componentStack.pop();
|
componentStack.pop();
|
||||||
|
} else if (fullName == "canvas.poses.pose") {
|
||||||
|
snapshot->poses.push_back(currentPose);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
|
@ -33,8 +33,9 @@ int Theme::toolIconFontSize = 16;
|
||||||
int Theme::toolIconSize = 24;
|
int Theme::toolIconSize = 24;
|
||||||
int Theme::miniIconFontSize = 9;
|
int Theme::miniIconFontSize = 9;
|
||||||
int Theme::miniIconSize = 15;
|
int Theme::miniIconSize = 15;
|
||||||
int Theme::previewImageSize = (Theme::miniIconSize * 3);
|
int Theme::partPreviewImageSize = (Theme::miniIconSize * 3);
|
||||||
int Theme::previewImageRenderSize = 75;
|
int Theme::posePreviewImageSize = 75;
|
||||||
|
int Theme::sidebarPreferredWidth = 200;
|
||||||
|
|
||||||
QtAwesome *Theme::awesome()
|
QtAwesome *Theme::awesome()
|
||||||
{
|
{
|
||||||
|
@ -128,3 +129,22 @@ void Theme::initAwesomeToolButton(QPushButton *button)
|
||||||
Theme::initAwesomeToolButtonWithoutFont(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;
|
||||||
|
}
|
||||||
|
|
|
@ -30,12 +30,15 @@ public:
|
||||||
static std::map<QString, QString> nextSideColorNameMap;
|
static std::map<QString, QString> nextSideColorNameMap;
|
||||||
static std::map<QString, QColor> sideColorNameToColorMap;
|
static std::map<QString, QColor> sideColorNameToColorMap;
|
||||||
static QtAwesome *awesome();
|
static QtAwesome *awesome();
|
||||||
|
static QWidget *createHorizontalLineWidget();
|
||||||
|
static QWidget *createVerticalLineWidget();
|
||||||
static int toolIconFontSize;
|
static int toolIconFontSize;
|
||||||
static int toolIconSize;
|
static int toolIconSize;
|
||||||
static int previewImageRenderSize;
|
static int posePreviewImageSize;
|
||||||
static int previewImageSize;
|
static int partPreviewImageSize;
|
||||||
static int miniIconFontSize;
|
static int miniIconFontSize;
|
||||||
static int miniIconSize;
|
static int miniIconSize;
|
||||||
|
static int sidebarPreferredWidth;
|
||||||
public:
|
public:
|
||||||
static void initAwesomeButton(QPushButton *button);
|
static void initAwesomeButton(QPushButton *button);
|
||||||
static void initAwesomeLabel(QLabel *label);
|
static void initAwesomeLabel(QLabel *label);
|
||||||
|
|
Loading…
Reference in New Issue