parent
b99e0db0b2
commit
af451bd5ab
48
dust3d.pro
48
dust3d.pro
|
@ -2,10 +2,9 @@ QT += core widgets opengl
|
|||
CONFIG += debug
|
||||
RESOURCES += resources.qrc
|
||||
|
||||
INCLUDEPATH += src
|
||||
include(thirdparty/QtAwesome/QtAwesome/QtAwesome.pri)
|
||||
|
||||
SOURCES += src/mainwindow.cpp
|
||||
HEADERS += src/mainwindow.h
|
||||
INCLUDEPATH += src
|
||||
|
||||
SOURCES += src/modelshaderprogram.cpp
|
||||
HEADERS += src/modelshaderprogram.h
|
||||
|
@ -13,27 +12,42 @@ HEADERS += src/modelshaderprogram.h
|
|||
SOURCES += src/modelmeshbinder.cpp
|
||||
HEADERS += src/modelmeshbinder.h
|
||||
|
||||
SOURCES += src/modelofflinerender.cpp
|
||||
HEADERS += src/modelofflinerender.h
|
||||
|
||||
SOURCES += src/modelwidget.cpp
|
||||
HEADERS += src/modelwidget.h
|
||||
|
||||
SOURCES += src/skeletoneditgraphicsview.cpp
|
||||
HEADERS += src/skeletoneditgraphicsview.h
|
||||
SOURCES += src/skeletonnodepropertywidget.cpp
|
||||
HEADERS += src/skeletonnodepropertywidget.h
|
||||
|
||||
SOURCES += src/skeletoneditnodeitem.cpp
|
||||
HEADERS += src/skeletoneditnodeitem.h
|
||||
SOURCES += src/skeletonedgepropertywidget.cpp
|
||||
HEADERS += src/skeletonedgepropertywidget.h
|
||||
|
||||
SOURCES += src/skeletoneditedgeitem.cpp
|
||||
HEADERS += src/skeletoneditedgeitem.h
|
||||
SOURCES += src/skeletondocument.cpp
|
||||
HEADERS += src/skeletondocument.h
|
||||
|
||||
SOURCES += src/skeletontomesh.cpp
|
||||
HEADERS += src/skeletontomesh.h
|
||||
SOURCES += src/skeletondocumentwindow.cpp
|
||||
HEADERS += src/skeletondocumentwindow.h
|
||||
|
||||
SOURCES += src/skeletongraphicswidget.cpp
|
||||
HEADERS += src/skeletongraphicswidget.h
|
||||
|
||||
SOURCES += src/skeletonhistorylistwidget.cpp
|
||||
HEADERS += src/skeletonhistorylistwidget.h
|
||||
|
||||
SOURCES += src/skeletonpartlistwidget.cpp
|
||||
HEADERS += src/skeletonpartlistwidget.h
|
||||
|
||||
SOURCES += src/meshgenerator.cpp
|
||||
HEADERS += src/meshgenerator.h
|
||||
|
||||
SOURCES += src/util.cpp
|
||||
HEADERS += src/util.h
|
||||
|
||||
SOURCES += src/turnaroundloader.cpp
|
||||
HEADERS += src/turnaroundloader.h
|
||||
|
||||
SOURCES += src/skeletonwidget.cpp
|
||||
HEADERS += src/skeletonwidget.h
|
||||
|
||||
SOURCES += src/skeletonsnapshot.cpp
|
||||
HEADERS += src/skeletonsnapshot.h
|
||||
|
||||
|
@ -52,6 +66,12 @@ HEADERS += src/mesh.h
|
|||
SOURCES += src/unionmesh.cpp
|
||||
HEADERS += src/unionmesh.h
|
||||
|
||||
SOURCES += src/logbrowser.cpp
|
||||
HEADERS += src/logbrowser.h
|
||||
|
||||
SOURCES += src/logbrowserdialog.cpp
|
||||
HEADERS += src/logbrowserdialog.h
|
||||
|
||||
SOURCES += src/main.cpp
|
||||
|
||||
INCLUDEPATH += ../meshlite/include
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
#include "logbrowser.h"
|
||||
// Modified from https://wiki.qt.io/Browser_for_QDebug_output
|
||||
#include <QMetaType>
|
||||
#include "logbrowserdialog.h"
|
||||
|
||||
LogBrowser::LogBrowser(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
qRegisterMetaType<QtMsgType>("QtMsgType");
|
||||
m_browserDialog = new LogBrowserDialog;
|
||||
connect(this, SIGNAL(sendMessage(QtMsgType,QString)), m_browserDialog, SLOT(outputMessage(QtMsgType,QString)), Qt::QueuedConnection);
|
||||
m_browserDialog->show();
|
||||
}
|
||||
|
||||
LogBrowser::~LogBrowser()
|
||||
{
|
||||
delete m_browserDialog;
|
||||
}
|
||||
|
||||
void LogBrowser::outputMessage(QtMsgType type, const QString &msg)
|
||||
{
|
||||
printf("%s\n", msg.toUtf8().constData());
|
||||
emit sendMessage( type, msg );
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef LOG_BROWSER_H
|
||||
#define LOG_BROWSER_H
|
||||
// Modified from https://wiki.qt.io/Browser_for_QDebug_output
|
||||
#include <QObject>
|
||||
|
||||
class LogBrowserDialog;
|
||||
|
||||
class LogBrowser : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LogBrowser(QObject *parent = 0);
|
||||
~LogBrowser();
|
||||
|
||||
public slots:
|
||||
void outputMessage(QtMsgType type, const QString &msg);
|
||||
|
||||
signals:
|
||||
void sendMessage(QtMsgType type, const QString &msg);
|
||||
|
||||
private:
|
||||
LogBrowserDialog *m_browserDialog;
|
||||
};
|
||||
|
||||
#endif // LOGBROWSER_H
|
|
@ -0,0 +1,120 @@
|
|||
#include "logbrowserdialog.h"
|
||||
// Modified from https://wiki.qt.io/Browser_for_QDebug_output
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QTextBrowser>
|
||||
#include <QPushButton>
|
||||
#include <QFileDialog>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <QTextStream>
|
||||
#include <QCloseEvent>
|
||||
#include <QKeyEvent>
|
||||
|
||||
LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
|
||||
QDialog(parent)
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
setLayout(layout);
|
||||
|
||||
m_browser = new QTextBrowser(this);
|
||||
layout->addWidget(m_browser);
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
buttonLayout->addStretch(10);
|
||||
|
||||
m_clearButton = new QPushButton(this);
|
||||
m_clearButton->setText("Clear");
|
||||
buttonLayout->addWidget(m_clearButton);
|
||||
connect(m_clearButton, SIGNAL(clicked()), m_browser, SLOT(clear()));
|
||||
|
||||
m_saveButton = new QPushButton(this);
|
||||
m_saveButton->setText("Save Output");
|
||||
buttonLayout->addWidget(m_saveButton);
|
||||
connect(m_saveButton, SIGNAL(clicked()), this, SLOT(save()));
|
||||
|
||||
resize(400, 300);
|
||||
}
|
||||
|
||||
|
||||
LogBrowserDialog::~LogBrowserDialog()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void LogBrowserDialog::outputMessage(QtMsgType type, const QString &msg)
|
||||
{
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
m_browser->append(tr("— DEBUG: %1").arg(msg));
|
||||
break;
|
||||
|
||||
case QtWarningMsg:
|
||||
m_browser->append(tr("— WARNING: %1").arg(msg));
|
||||
break;
|
||||
|
||||
case QtCriticalMsg:
|
||||
m_browser->append(tr("— CRITICAL: %1").arg(msg));
|
||||
break;
|
||||
|
||||
case QtInfoMsg:
|
||||
m_browser->append(tr("— INFO: %1").arg(msg));
|
||||
break;
|
||||
|
||||
case QtFatalMsg:
|
||||
m_browser->append(tr("— FATAL: %1").arg(msg));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LogBrowserDialog::save()
|
||||
{
|
||||
QString saveFileName = QFileDialog::getSaveFileName(this,
|
||||
tr("Save Log Output"),
|
||||
tr("%1/logfile.txt").arg(QDir::homePath()),
|
||||
tr("Text Files (*.txt);;All Files (*)"));
|
||||
|
||||
if (saveFileName.isEmpty())
|
||||
return;
|
||||
|
||||
QFile file(saveFileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
QMessageBox::warning(this,
|
||||
tr("Error"),
|
||||
QString(tr("<nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>"
|
||||
"The log output could <b>not</b> be saved!</nobr>"))
|
||||
.arg(saveFileName));
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
stream << m_browser->toPlainText();
|
||||
file.close();
|
||||
}
|
||||
|
||||
void LogBrowserDialog::closeEvent(QCloseEvent *e)
|
||||
{
|
||||
QMessageBox::StandardButton answer = QMessageBox::question(this,
|
||||
tr("Close Log Browser?"),
|
||||
tr("Do you really want to close the log browser?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (answer == QMessageBox::Yes)
|
||||
e->accept();
|
||||
else
|
||||
e->ignore();
|
||||
}
|
||||
|
||||
void LogBrowserDialog::keyPressEvent(QKeyEvent *e)
|
||||
{
|
||||
// ignore all keyboard events
|
||||
// protects against accidentally closing of the dialog
|
||||
// without asking the user
|
||||
e->ignore();
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef LOG_BROWSER_DIALOG_H
|
||||
#define LOG_BROWSER_DIALOG_H
|
||||
// Modified from https://wiki.qt.io/Browser_for_QDebug_output
|
||||
#include <QDialog>
|
||||
|
||||
class QTextBrowser;
|
||||
class QPushButton;
|
||||
|
||||
class LogBrowserDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
LogBrowserDialog(QWidget *parent = 0);
|
||||
~LogBrowserDialog();
|
||||
|
||||
public slots:
|
||||
void outputMessage(QtMsgType type, const QString &msg);
|
||||
|
||||
protected slots:
|
||||
void save();
|
||||
|
||||
protected:
|
||||
virtual void keyPressEvent(QKeyEvent *e);
|
||||
virtual void closeEvent(QCloseEvent *e);
|
||||
|
||||
QTextBrowser *m_browser;
|
||||
QPushButton *m_clearButton;
|
||||
QPushButton *m_saveButton;
|
||||
};
|
||||
|
||||
#endif // DIALOG_H
|
25
src/main.cpp
25
src/main.cpp
|
@ -2,8 +2,20 @@
|
|||
#include <QDesktopWidget>
|
||||
#include <QStyleFactory>
|
||||
#include <QFontDatabase>
|
||||
#include "mainwindow.h"
|
||||
#include "meshlite.h"
|
||||
#include <QPointer>
|
||||
#include <QDebug>
|
||||
#include <QtGlobal>
|
||||
#include "logbrowser.h"
|
||||
#include "skeletondocumentwindow.h"
|
||||
#include "theme.h"
|
||||
|
||||
QPointer<LogBrowser> g_logBrowser;
|
||||
|
||||
void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
if (g_logBrowser)
|
||||
g_logBrowser->outputMessage(type, msg);
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
|
@ -24,8 +36,8 @@ int main(int argc, char ** argv)
|
|||
darkPalette.setColor(QPalette::ButtonText, QColor(239,239,239));
|
||||
darkPalette.setColor(QPalette::BrightText, Qt::red);
|
||||
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
|
||||
darkPalette.setColor(QPalette::Highlight, QColor(252, 102, 33));
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::black);
|
||||
darkPalette.setColor(QPalette::Highlight, Theme::white);
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
qApp->setPalette(darkPalette);
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #fc6621; border: 1px solid white; }");
|
||||
|
||||
|
@ -36,8 +48,11 @@ int main(int argc, char ** argv)
|
|||
font.setPixelSize(9);
|
||||
font.setBold(false);
|
||||
QApplication::setFont(font);
|
||||
|
||||
g_logBrowser = new LogBrowser;
|
||||
qInstallMessageHandler(&outputMessage);
|
||||
|
||||
MainWindow mainWindow;
|
||||
SkeletonDocumentWindow mainWindow;
|
||||
mainWindow.showMaximized();
|
||||
return app.exec();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
#include <vector>
|
||||
#include <QGuiApplication>
|
||||
#include "meshgenerator.h"
|
||||
#include "util.h"
|
||||
#include "skeletondocument.h"
|
||||
#include "meshlite.h"
|
||||
#include "modelofflinerender.h"
|
||||
#include "unionmesh.h"
|
||||
#include "theme.h"
|
||||
|
||||
MeshGenerator::MeshGenerator(SkeletonSnapshot *snapshot, QThread *thread) :
|
||||
m_snapshot(snapshot),
|
||||
m_mesh(nullptr),
|
||||
m_preview(nullptr),
|
||||
m_requirePreview(false),
|
||||
m_previewRender(nullptr),
|
||||
m_thread(thread)
|
||||
{
|
||||
}
|
||||
|
||||
MeshGenerator::~MeshGenerator()
|
||||
{
|
||||
delete m_snapshot;
|
||||
delete m_mesh;
|
||||
delete m_preview;
|
||||
for (const auto &partPreviewIt: m_partPreviewMap) {
|
||||
delete partPreviewIt.second;
|
||||
}
|
||||
for (const auto &render: m_partPreviewRenderMap) {
|
||||
delete render.second;
|
||||
}
|
||||
delete m_previewRender;
|
||||
}
|
||||
|
||||
void MeshGenerator::addPreviewRequirement()
|
||||
{
|
||||
m_requirePreview = true;
|
||||
if (nullptr == m_previewRender) {
|
||||
m_previewRender = new ModelOfflineRender;
|
||||
m_previewRender->setRenderThread(m_thread);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshGenerator::addPartPreviewRequirement(const QString &partId)
|
||||
{
|
||||
qDebug() << "addPartPreviewRequirement:" << partId;
|
||||
m_requirePartPreviewMap.insert(partId);
|
||||
if (m_partPreviewRenderMap.find(partId) == m_partPreviewRenderMap.end()) {
|
||||
ModelOfflineRender *render = new ModelOfflineRender;
|
||||
render->setRenderThread(m_thread);
|
||||
m_partPreviewRenderMap[partId] = render;
|
||||
}
|
||||
}
|
||||
|
||||
Mesh *MeshGenerator::takeResultMesh()
|
||||
{
|
||||
Mesh *resultMesh = m_mesh;
|
||||
m_mesh = nullptr;
|
||||
return resultMesh;
|
||||
}
|
||||
|
||||
QImage *MeshGenerator::takePreview()
|
||||
{
|
||||
QImage *resultPreview = m_preview;
|
||||
m_preview = nullptr;
|
||||
return resultPreview;
|
||||
}
|
||||
|
||||
QImage *MeshGenerator::takePartPreview(const QString &partId)
|
||||
{
|
||||
QImage *resultImage = m_partPreviewMap[partId];
|
||||
m_partPreviewMap[partId] = nullptr;
|
||||
return resultImage;
|
||||
}
|
||||
|
||||
void MeshGenerator::resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile)
|
||||
{
|
||||
float left = -1;
|
||||
float right = -1;
|
||||
float top = -1;
|
||||
float bottom = -1;
|
||||
float zLeft = -1;
|
||||
float zRight = -1;
|
||||
for (const auto &nodeIt: m_snapshot->nodes) {
|
||||
float radius = valueOfKeyInMapOrEmpty(nodeIt.second, "radius").toFloat();
|
||||
float x = valueOfKeyInMapOrEmpty(nodeIt.second, "x").toFloat();
|
||||
float y = valueOfKeyInMapOrEmpty(nodeIt.second, "y").toFloat();
|
||||
float z = valueOfKeyInMapOrEmpty(nodeIt.second, "z").toFloat();
|
||||
if (left < 0 || x - radius < left) {
|
||||
left = x - radius;
|
||||
}
|
||||
if (top < 0 || y - radius < top) {
|
||||
top = y - radius;
|
||||
}
|
||||
if (x + radius > right) {
|
||||
right = x + radius;
|
||||
}
|
||||
if (y + radius > bottom) {
|
||||
bottom = y + radius;
|
||||
}
|
||||
if (zLeft < 0 || z - radius < zLeft) {
|
||||
zLeft = z - radius;
|
||||
}
|
||||
if (z + radius > zRight) {
|
||||
zRight = z + radius;
|
||||
}
|
||||
}
|
||||
*mainProfile = QRectF(left, top, right - left, bottom - top);
|
||||
*sideProfile = QRectF(zLeft, top, zRight - zLeft, bottom - top);
|
||||
}
|
||||
|
||||
void MeshGenerator::process()
|
||||
{
|
||||
if (nullptr == m_snapshot)
|
||||
return;
|
||||
|
||||
void *meshliteContext = meshlite_create_context();
|
||||
std::map<QString, int> partBmeshMap;
|
||||
std::map<QString, int> bmeshNodeMap;
|
||||
|
||||
QRectF mainProfile, sideProfile;
|
||||
resolveBoundingBox(&mainProfile, &sideProfile);
|
||||
float longHeight = mainProfile.height();
|
||||
if (mainProfile.width() > longHeight)
|
||||
longHeight = mainProfile.width();
|
||||
if (sideProfile.width() > longHeight)
|
||||
longHeight = sideProfile.width();
|
||||
float mainProfileMiddleX = mainProfile.x() + mainProfile.width() / 2;
|
||||
float sideProfileMiddleX = sideProfile.x() + sideProfile.width() / 2;
|
||||
float mainProfileMiddleY = mainProfile.y() + mainProfile.height() / 2;
|
||||
|
||||
for (const auto &partIdIt: m_snapshot->partIdList) {
|
||||
int bmeshId = meshlite_bmesh_create(meshliteContext);
|
||||
partBmeshMap[partIdIt] = bmeshId;
|
||||
}
|
||||
|
||||
for (const auto &edgeIt: m_snapshot->edges) {
|
||||
QString partId = valueOfKeyInMapOrEmpty(edgeIt.second, "partId");
|
||||
QString fromNodeId = valueOfKeyInMapOrEmpty(edgeIt.second, "from");
|
||||
QString toNodeId = valueOfKeyInMapOrEmpty(edgeIt.second, "to");
|
||||
qDebug() << "Processing edge " << fromNodeId << "<=>" << toNodeId;
|
||||
const auto fromIt = m_snapshot->nodes.find(fromNodeId);
|
||||
const auto toIt = m_snapshot->nodes.find(toNodeId);
|
||||
if (fromIt == m_snapshot->nodes.end() || toIt == m_snapshot->nodes.end())
|
||||
continue;
|
||||
const auto partBmeshIt = partBmeshMap.find(partId);
|
||||
if (partBmeshIt == partBmeshMap.end())
|
||||
continue;
|
||||
int bmeshId = partBmeshIt->second;
|
||||
|
||||
int bmeshFromNodeId = 0;
|
||||
const auto bmeshFromIt = bmeshNodeMap.find(fromNodeId);
|
||||
if (bmeshFromIt == bmeshNodeMap.end()) {
|
||||
float radius = valueOfKeyInMapOrEmpty(fromIt->second, "radius").toFloat() / longHeight;
|
||||
float x = (valueOfKeyInMapOrEmpty(fromIt->second, "x").toFloat() - mainProfileMiddleX) / longHeight;
|
||||
float y = (valueOfKeyInMapOrEmpty(fromIt->second, "y").toFloat() - mainProfileMiddleY) / longHeight;
|
||||
float z = (valueOfKeyInMapOrEmpty(fromIt->second, "z").toFloat() - sideProfileMiddleX) / longHeight;
|
||||
bmeshFromNodeId = meshlite_bmesh_add_node(meshliteContext, bmeshId, x, y, z, radius);
|
||||
qDebug() << "bmeshId[" << bmeshId << "] add node[" << bmeshFromNodeId << "]" << radius << x << y << z;
|
||||
bmeshNodeMap[fromNodeId] = bmeshFromNodeId;
|
||||
} else {
|
||||
bmeshFromNodeId = bmeshFromIt->second;
|
||||
qDebug() << "bmeshId[" << bmeshId << "] use existed node[" << bmeshFromNodeId << "]";
|
||||
}
|
||||
|
||||
int bmeshToNodeId = 0;
|
||||
const auto bmeshToIt = bmeshNodeMap.find(toNodeId);
|
||||
if (bmeshToIt == bmeshNodeMap.end()) {
|
||||
float radius = valueOfKeyInMapOrEmpty(toIt->second, "radius").toFloat() / longHeight;
|
||||
float x = (valueOfKeyInMapOrEmpty(toIt->second, "x").toFloat() - mainProfileMiddleX) / longHeight;
|
||||
float y = (valueOfKeyInMapOrEmpty(toIt->second, "y").toFloat() - mainProfileMiddleY) / longHeight;
|
||||
float z = (valueOfKeyInMapOrEmpty(toIt->second, "z").toFloat() - sideProfileMiddleX) / longHeight;
|
||||
bmeshToNodeId = meshlite_bmesh_add_node(meshliteContext, bmeshId, x, y, z, radius);
|
||||
qDebug() << "bmeshId[" << bmeshId << "] add node[" << bmeshToNodeId << "]" << radius << x << y << z;
|
||||
bmeshNodeMap[toNodeId] = bmeshToNodeId;
|
||||
} else {
|
||||
bmeshToNodeId = bmeshToIt->second;
|
||||
qDebug() << "bmeshId[" << bmeshId << "] use existed node[" << bmeshToNodeId << "]";
|
||||
}
|
||||
|
||||
meshlite_bmesh_add_edge(meshliteContext, bmeshId, bmeshFromNodeId, bmeshToNodeId);
|
||||
}
|
||||
|
||||
std::map<QString, int> partMeshMap;
|
||||
std::vector<int> meshIds;
|
||||
for (const auto &partIdIt: m_snapshot->partIdList) {
|
||||
int bmeshId = partBmeshMap[partIdIt];
|
||||
int meshId = meshlite_bmesh_generate_mesh(meshliteContext, bmeshId, 0);
|
||||
qDebug() << "m_requirePartPreviewMap.find:" << partIdIt;
|
||||
if (m_requirePartPreviewMap.find(partIdIt) != m_requirePartPreviewMap.end()) {
|
||||
ModelOfflineRender *render = m_partPreviewRenderMap[partIdIt];
|
||||
render->updateMesh(new Mesh(meshliteContext, meshId));
|
||||
QImage *image = new QImage(render->toImage(QSize(Theme::previewImageSize, Theme::previewImageSize)));
|
||||
m_partPreviewMap[partIdIt] = image;
|
||||
}
|
||||
int triangulatedMeshId = meshlite_triangulate(meshliteContext, meshId);
|
||||
meshIds.push_back(triangulatedMeshId);
|
||||
}
|
||||
|
||||
int mergedMeshId = 0;
|
||||
if (meshIds.size() > 0) {
|
||||
mergedMeshId = unionMeshs(meshliteContext, meshIds);
|
||||
if (mergedMeshId > 0) {
|
||||
mergedMeshId = meshlite_combine_coplanar_faces(meshliteContext, mergedMeshId);
|
||||
}
|
||||
}
|
||||
|
||||
if (mergedMeshId > 0) {
|
||||
if (m_requirePreview) {
|
||||
m_previewRender->updateMesh(new Mesh(meshliteContext, mergedMeshId));
|
||||
QImage *image = new QImage(m_previewRender->toImage(QSize(Theme::previewImageSize, Theme::previewImageSize)));
|
||||
m_preview = image;
|
||||
}
|
||||
m_mesh = new Mesh(meshliteContext, mergedMeshId);
|
||||
}
|
||||
|
||||
if (m_previewRender) {
|
||||
m_previewRender->setRenderThread(QGuiApplication::instance()->thread());
|
||||
}
|
||||
|
||||
for (auto &partPreviewRender: m_partPreviewRenderMap) {
|
||||
partPreviewRender.second->setRenderThread(QGuiApplication::instance()->thread());
|
||||
}
|
||||
|
||||
meshlite_destroy_context(meshliteContext);
|
||||
|
||||
this->moveToThread(QGuiApplication::instance()->thread());
|
||||
|
||||
emit finished();
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
#ifndef MESH_GENERATOR_H
|
||||
#define MESH_GENERATOR_H
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <QThread>
|
||||
#include "skeletonsnapshot.h"
|
||||
#include "mesh.h"
|
||||
#include "modelofflinerender.h"
|
||||
|
||||
class MeshGenerator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MeshGenerator(SkeletonSnapshot *snapshot, QThread *thread);
|
||||
~MeshGenerator();
|
||||
void addPreviewRequirement();
|
||||
void addPartPreviewRequirement(const QString &partId);
|
||||
Mesh *takeResultMesh();
|
||||
QImage *takePreview();
|
||||
QImage *takePartPreview(const QString &partId);
|
||||
signals:
|
||||
void finished();
|
||||
public slots:
|
||||
void process();
|
||||
private:
|
||||
SkeletonSnapshot *m_snapshot;
|
||||
Mesh *m_mesh;
|
||||
QImage *m_preview;
|
||||
std::map<QString, QImage *> m_partPreviewMap;
|
||||
bool m_requirePreview;
|
||||
std::set<QString> m_requirePartPreviewMap;
|
||||
ModelOfflineRender *m_previewRender;
|
||||
std::map<QString, ModelOfflineRender *> m_partPreviewRenderMap;
|
||||
QThread *m_thread;
|
||||
private:
|
||||
void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,107 @@
|
|||
#include <QOpenGLFramebufferObjectFormat>
|
||||
#include <QThread>
|
||||
#include <QDebug>
|
||||
#include "modelofflinerender.h"
|
||||
|
||||
ModelOfflineRender::ModelOfflineRender(QScreen *targetScreen) :
|
||||
QOffscreenSurface(targetScreen),
|
||||
m_context(nullptr),
|
||||
m_mesh(nullptr)
|
||||
{
|
||||
create();
|
||||
m_context = new QOpenGLContext();
|
||||
m_context->setFormat(format());
|
||||
m_context->create();
|
||||
}
|
||||
|
||||
ModelOfflineRender::~ModelOfflineRender()
|
||||
{
|
||||
delete m_context;
|
||||
m_context = nullptr;
|
||||
destroy();
|
||||
delete m_mesh;
|
||||
}
|
||||
|
||||
void ModelOfflineRender::updateMesh(Mesh *mesh)
|
||||
{
|
||||
delete m_mesh;
|
||||
m_mesh = mesh;
|
||||
}
|
||||
|
||||
void ModelOfflineRender::setRenderThread(QThread *thread)
|
||||
{
|
||||
m_context->moveToThread(thread);
|
||||
}
|
||||
|
||||
QImage ModelOfflineRender::toImage(const QSize &size)
|
||||
{
|
||||
QImage image;
|
||||
|
||||
m_context->makeCurrent(this);
|
||||
|
||||
QOpenGLFramebufferObjectFormat format;
|
||||
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
|
||||
QOpenGLFramebufferObject *renderFbo = new QOpenGLFramebufferObject(size, format);
|
||||
renderFbo->bind();
|
||||
m_context->functions()->glViewport(0, 0, size.width(), size.height());
|
||||
|
||||
if (nullptr != m_mesh) {
|
||||
int xRot = 0;
|
||||
int yRot = 0;
|
||||
int zRot = 0;
|
||||
QMatrix4x4 proj;
|
||||
QMatrix4x4 camera;
|
||||
QMatrix4x4 world;
|
||||
|
||||
ModelShaderProgram *program = new ModelShaderProgram;
|
||||
ModelMeshBinder meshBinder;
|
||||
meshBinder.initialize();
|
||||
|
||||
program->setUniformValue(program->lightPosLoc(), QVector3D(0, 0, 70));
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_CULL_FACE);
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
|
||||
camera.setToIdentity();
|
||||
camera.translate(0, 0, -2.5);
|
||||
|
||||
world.setToIdentity();
|
||||
world.rotate(180.0f - (xRot / 16.0f), 1, 0, 0);
|
||||
world.rotate(yRot / 16.0f, 0, 1, 0);
|
||||
world.rotate(zRot / 16.0f, 0, 0, 1);
|
||||
|
||||
proj.setToIdentity();
|
||||
proj.perspective(45.0f, GLfloat(size.width()) / size.height(), 0.01f, 100.0f);
|
||||
|
||||
program->bind();
|
||||
program->setUniformValue(program->projMatrixLoc(), proj);
|
||||
program->setUniformValue(program->mvMatrixLoc(), camera * world);
|
||||
QMatrix3x3 normalMatrix = world.normalMatrix();
|
||||
program->setUniformValue(program->normalMatrixLoc(), normalMatrix);
|
||||
|
||||
meshBinder.updateMesh(m_mesh);
|
||||
meshBinder.paint();
|
||||
|
||||
meshBinder.cleanup();
|
||||
|
||||
program->release();
|
||||
delete program;
|
||||
|
||||
m_mesh = nullptr;
|
||||
}
|
||||
|
||||
m_context->functions()->glFlush();
|
||||
|
||||
image = renderFbo->toImage();
|
||||
|
||||
qDebug() << "Generated image size:" << image.size();
|
||||
|
||||
renderFbo->bindDefault();
|
||||
delete renderFbo;
|
||||
|
||||
m_context->doneCurrent();
|
||||
|
||||
return image;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef MODEL_OFFLINE_RENDER_H
|
||||
#define MODEL_OFFLINE_RENDER_H
|
||||
#include <QOffscreenSurface>
|
||||
#include <QScreen>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLContext>
|
||||
#include <QImage>
|
||||
#include <QThread>
|
||||
#include "modelshaderprogram.h"
|
||||
#include "modelmeshbinder.h"
|
||||
#include "mesh.h"
|
||||
|
||||
class ModelOfflineRender : QOffscreenSurface
|
||||
{
|
||||
public:
|
||||
ModelOfflineRender(QScreen *targetScreen = Q_NULLPTR);
|
||||
~ModelOfflineRender();
|
||||
void setRenderThread(QThread *thread);
|
||||
void updateMesh(Mesh *mesh);
|
||||
QImage toImage(const QSize &size);
|
||||
private:
|
||||
QOpenGLContext *m_context;
|
||||
Mesh *m_mesh;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,6 +1,6 @@
|
|||
#include "modelwidget.h"
|
||||
#include "ds3file.h"
|
||||
#include "skeletoneditgraphicsview.h"
|
||||
#include "skeletongraphicswidget.h"
|
||||
#include <QMouseEvent>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QCoreApplication>
|
||||
|
@ -16,9 +16,9 @@ ModelWidget::ModelWidget(QWidget *parent)
|
|||
m_xRot(0),
|
||||
m_yRot(0),
|
||||
m_zRot(0),
|
||||
m_program(NULL),
|
||||
m_program(nullptr),
|
||||
m_moveStarted(false),
|
||||
m_graphicsView(NULL)
|
||||
m_graphicsFunctions(NULL)
|
||||
{
|
||||
// --transparent causes the clear color to be transparent. Therefore, on systems that
|
||||
// support it, the widget will become transparent apart from the logo.
|
||||
|
@ -34,11 +34,12 @@ ModelWidget::ModelWidget(QWidget *parent)
|
|||
fmt.setSamples(4);
|
||||
setFormat(fmt);
|
||||
}
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void ModelWidget::setGraphicsView(SkeletonEditGraphicsView *view)
|
||||
void ModelWidget::setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions)
|
||||
{
|
||||
m_graphicsView = view;
|
||||
m_graphicsFunctions = graphicsFunctions;
|
||||
}
|
||||
|
||||
ModelWidget::~ModelWidget()
|
||||
|
@ -91,7 +92,7 @@ void ModelWidget::cleanup()
|
|||
makeCurrent();
|
||||
m_meshBinder.cleanup();
|
||||
delete m_program;
|
||||
m_program = 0;
|
||||
m_program = nullptr;
|
||||
doneCurrent();
|
||||
}
|
||||
|
||||
|
@ -124,7 +125,7 @@ void ModelWidget::initializeGL()
|
|||
|
||||
// Our camera never changes in this example.
|
||||
m_camera.setToIdentity();
|
||||
m_camera.translate(0, 0, -2.5);
|
||||
m_camera.translate(0, 0, -3.5);
|
||||
|
||||
// Light position is fixed.
|
||||
m_program->setUniformValue(m_program->lightPosLoc(), QVector3D(0, 0, 70));
|
||||
|
@ -164,10 +165,9 @@ void ModelWidget::resizeGL(int w, int h)
|
|||
|
||||
void ModelWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!m_moveStarted && m_graphicsView && m_graphicsView->mousePress(event, m_graphicsView->mapToScene(m_graphicsView->mapFromGlobal(event->globalPos()))))
|
||||
if (!m_moveStarted && m_graphicsFunctions && m_graphicsFunctions->mousePress(event))
|
||||
return;
|
||||
m_lastPos = event->pos();
|
||||
setMouseTracking(true);
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (!m_moveStarted) {
|
||||
m_moveStartPos = mapToParent(event->pos());
|
||||
|
@ -179,7 +179,7 @@ void ModelWidget::mousePressEvent(QMouseEvent *event)
|
|||
|
||||
void ModelWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!m_moveStarted && m_graphicsView && m_graphicsView->mouseRelease(event, m_graphicsView->mapToScene(m_graphicsView->mapFromGlobal(event->globalPos()))))
|
||||
if (!m_moveStarted && m_graphicsFunctions && m_graphicsFunctions->mouseRelease(event))
|
||||
return;
|
||||
if (m_moveStarted) {
|
||||
m_moveStarted = false;
|
||||
|
@ -188,7 +188,7 @@ void ModelWidget::mouseReleaseEvent(QMouseEvent *event)
|
|||
|
||||
void ModelWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!m_moveStarted && m_graphicsView && m_graphicsView->mouseMove(event, m_graphicsView->mapToScene(m_graphicsView->mapFromGlobal(event->globalPos()))))
|
||||
if (!m_moveStarted && m_graphicsFunctions && m_graphicsFunctions->mouseMove(event))
|
||||
return;
|
||||
|
||||
int dx = event->x() - m_lastPos.x();
|
||||
|
@ -210,7 +210,7 @@ void ModelWidget::mouseMoveEvent(QMouseEvent *event)
|
|||
|
||||
void ModelWidget::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
if (!m_moveStarted && m_graphicsView && m_graphicsView->wheel(event, m_graphicsView->mapToScene(m_graphicsView->mapFromGlobal(event->globalPos()))))
|
||||
if (!m_moveStarted && m_graphicsFunctions && m_graphicsFunctions->wheel(event))
|
||||
return;
|
||||
if (m_moveStarted)
|
||||
return;
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include "modelshaderprogram.h"
|
||||
#include "modelmeshbinder.h"
|
||||
|
||||
class SkeletonEditGraphicsView;
|
||||
class SkeletonGraphicsFunctions;
|
||||
|
||||
class ModelWidget : public QOpenGLWidget, protected QOpenGLFunctions
|
||||
{
|
||||
|
@ -26,7 +26,7 @@ public:
|
|||
|
||||
void updateMesh(Mesh *mesh);
|
||||
void exportMeshAsObj(const QString &filename);
|
||||
void setGraphicsView(SkeletonEditGraphicsView *view);
|
||||
void setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions);
|
||||
|
||||
public slots:
|
||||
void setXRotation(int angle);
|
||||
|
@ -61,7 +61,7 @@ private:
|
|||
bool m_moveStarted;
|
||||
QPoint m_moveStartPos;
|
||||
QRect m_moveStartGeometry;
|
||||
SkeletonEditGraphicsView *m_graphicsView;
|
||||
SkeletonGraphicsFunctions *m_graphicsFunctions;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,706 @@
|
|||
#include <QFileDialog>
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
#include <QGuiApplication>
|
||||
#include "skeletondocument.h"
|
||||
#include "util.h"
|
||||
|
||||
SkeletonDocument::SkeletonDocument() :
|
||||
m_resultMeshIsObsolete(false),
|
||||
m_resultMesh(nullptr),
|
||||
m_meshGenerator(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
SkeletonDocument::~SkeletonDocument()
|
||||
{
|
||||
delete m_resultMesh;
|
||||
}
|
||||
|
||||
void SkeletonDocument::uiReady()
|
||||
{
|
||||
emit editModeChanged();
|
||||
}
|
||||
|
||||
void SkeletonDocument::removePart(QUuid partId)
|
||||
{
|
||||
}
|
||||
|
||||
void SkeletonDocument::showPart(QUuid partId)
|
||||
{
|
||||
}
|
||||
|
||||
void SkeletonDocument::hidePart(QUuid partId)
|
||||
{
|
||||
}
|
||||
|
||||
void SkeletonDocument::removeEdge(QUuid edgeId)
|
||||
{
|
||||
const SkeletonEdge *edge = findEdge(edgeId);
|
||||
if (nullptr == edge) {
|
||||
qDebug() << "Find edge failed:" << edgeId;
|
||||
return;
|
||||
}
|
||||
const SkeletonPart *oldPart = findPart(edge->partId);
|
||||
if (nullptr == oldPart) {
|
||||
qDebug() << "Find part failed:" << edge->partId;
|
||||
return;
|
||||
}
|
||||
QString nextPartName = oldPart->name;
|
||||
QUuid oldPartId = oldPart->id;
|
||||
std::vector<std::vector<QUuid>> groups;
|
||||
splitPartByEdge(&groups, edgeId);
|
||||
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
|
||||
SkeletonPart part;
|
||||
part.name = nextPartName;
|
||||
for (auto nodeIdIt = (*groupIt).begin(); nodeIdIt != (*groupIt).end(); nodeIdIt++) {
|
||||
auto nodeIt = nodeMap.find(*nodeIdIt);
|
||||
if (nodeIt == nodeMap.end()) {
|
||||
qDebug() << "Find node failed:" << *nodeIdIt;
|
||||
continue;
|
||||
}
|
||||
nodeIt->second.partId = part.id;
|
||||
part.nodeIds.push_back(nodeIt->first);
|
||||
for (auto edgeIdIt = nodeIt->second.edgeIds.begin(); edgeIdIt != nodeIt->second.edgeIds.end(); edgeIdIt++) {
|
||||
auto edgeIt = edgeMap.find(*edgeIdIt);
|
||||
if (edgeIt == edgeMap.end()) {
|
||||
qDebug() << "Find edge failed:" << *edgeIdIt;
|
||||
continue;
|
||||
}
|
||||
edgeIt->second.partId = part.id;
|
||||
}
|
||||
}
|
||||
partMap[part.id] = part;
|
||||
partIds.push_back(part.id);
|
||||
emit partAdded(part.id);
|
||||
}
|
||||
for (auto nodeIdIt = edge->nodeIds.begin(); nodeIdIt != edge->nodeIds.end(); nodeIdIt++) {
|
||||
auto nodeIt = nodeMap.find(*nodeIdIt);
|
||||
if (nodeIt == nodeMap.end()) {
|
||||
qDebug() << "Find node failed:" << *nodeIdIt;
|
||||
continue;
|
||||
}
|
||||
nodeIt->second.edgeIds.erase(std::remove(nodeIt->second.edgeIds.begin(), nodeIt->second.edgeIds.end(), edgeId), nodeIt->second.edgeIds.end());
|
||||
emit nodeOriginChanged(nodeIt->first);
|
||||
}
|
||||
edgeMap.erase(edgeId);
|
||||
emit edgeRemoved(edgeId);
|
||||
partIds.erase(std::remove(partIds.begin(), partIds.end(), oldPartId), partIds.end());
|
||||
partMap.erase(oldPartId);
|
||||
emit partRemoved(oldPartId);
|
||||
emit partListChanged();
|
||||
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void SkeletonDocument::removeNode(QUuid nodeId)
|
||||
{
|
||||
const SkeletonNode *node = findNode(nodeId);
|
||||
if (nullptr == node) {
|
||||
qDebug() << "Find node failed:" << nodeId;
|
||||
return;
|
||||
}
|
||||
const SkeletonPart *oldPart = findPart(node->partId);
|
||||
if (nullptr == oldPart) {
|
||||
qDebug() << "Find part failed:" << node->partId;
|
||||
return;
|
||||
}
|
||||
QString nextPartName = oldPart->name;
|
||||
QUuid oldPartId = oldPart->id;
|
||||
std::vector<std::vector<QUuid>> groups;
|
||||
splitPartByNode(&groups, nodeId);
|
||||
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
|
||||
SkeletonPart part;
|
||||
part.name = nextPartName;
|
||||
for (auto nodeIdIt = (*groupIt).begin(); nodeIdIt != (*groupIt).end(); nodeIdIt++) {
|
||||
auto nodeIt = nodeMap.find(*nodeIdIt);
|
||||
if (nodeIt == nodeMap.end()) {
|
||||
qDebug() << "Find node failed:" << *nodeIdIt;
|
||||
continue;
|
||||
}
|
||||
nodeIt->second.partId = part.id;
|
||||
part.nodeIds.push_back(nodeIt->first);
|
||||
for (auto edgeIdIt = nodeIt->second.edgeIds.begin(); edgeIdIt != nodeIt->second.edgeIds.end(); edgeIdIt++) {
|
||||
auto edgeIt = edgeMap.find(*edgeIdIt);
|
||||
if (edgeIt == edgeMap.end()) {
|
||||
qDebug() << "Find edge failed:" << *edgeIdIt;
|
||||
continue;
|
||||
}
|
||||
edgeIt->second.partId = part.id;
|
||||
}
|
||||
}
|
||||
partMap[part.id] = part;
|
||||
partIds.push_back(part.id);
|
||||
emit partAdded(part.id);
|
||||
}
|
||||
for (auto edgeIdIt = node->edgeIds.begin(); edgeIdIt != node->edgeIds.end(); edgeIdIt++) {
|
||||
auto edgeIt = edgeMap.find(*edgeIdIt);
|
||||
if (edgeIt == edgeMap.end()) {
|
||||
qDebug() << "Find edge failed:" << *edgeIdIt;
|
||||
continue;
|
||||
}
|
||||
QUuid neighborId = edgeIt->second.neighborOf(nodeId);
|
||||
auto nodeIt = nodeMap.find(neighborId);
|
||||
if (nodeIt == nodeMap.end()) {
|
||||
qDebug() << "Find node failed:" << neighborId;
|
||||
continue;
|
||||
}
|
||||
nodeIt->second.edgeIds.erase(std::remove(nodeIt->second.edgeIds.begin(), nodeIt->second.edgeIds.end(), *edgeIdIt), nodeIt->second.edgeIds.end());
|
||||
edgeMap.erase(*edgeIdIt);
|
||||
emit edgeRemoved(*edgeIdIt);
|
||||
}
|
||||
nodeMap.erase(nodeId);
|
||||
emit nodeRemoved(nodeId);
|
||||
partIds.erase(std::remove(partIds.begin(), partIds.end(), oldPartId), partIds.end());
|
||||
partMap.erase(oldPartId);
|
||||
emit partRemoved(oldPartId);
|
||||
emit partListChanged();
|
||||
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void SkeletonDocument::addNode(float x, float y, float z, float radius, QUuid fromNodeId)
|
||||
{
|
||||
QUuid partId;
|
||||
const SkeletonNode *fromNode = nullptr;
|
||||
bool newPartAdded = false;
|
||||
if (fromNodeId.isNull()) {
|
||||
SkeletonPart part;
|
||||
partMap[part.id] = part;
|
||||
partIds.push_back(part.id);
|
||||
partId = part.id;
|
||||
emit partAdded(partId);
|
||||
newPartAdded = true;
|
||||
} else {
|
||||
fromNode = findNode(fromNodeId);
|
||||
if (nullptr == fromNode) {
|
||||
qDebug() << "Find node failed:" << fromNodeId;
|
||||
return;
|
||||
}
|
||||
partId = fromNode->partId;
|
||||
}
|
||||
SkeletonNode node;
|
||||
node.partId = partId;
|
||||
node.radius = radius;
|
||||
node.x = x;
|
||||
node.y = y;
|
||||
node.z = z;
|
||||
nodeMap[node.id] = node;
|
||||
partMap[partId].nodeIds.push_back(node.id);
|
||||
|
||||
qDebug() << "Add node " << node.id << x << y << z;
|
||||
|
||||
emit nodeAdded(node.id);
|
||||
|
||||
if (nullptr != fromNode) {
|
||||
SkeletonEdge edge;
|
||||
edge.partId = partId;
|
||||
edge.nodeIds.push_back(fromNode->id);
|
||||
edge.nodeIds.push_back(node.id);
|
||||
edgeMap[edge.id] = edge;
|
||||
|
||||
nodeMap[node.id].edgeIds.push_back(edge.id);
|
||||
nodeMap[fromNode->id].edgeIds.push_back(edge.id);
|
||||
|
||||
emit edgeAdded(edge.id);
|
||||
}
|
||||
|
||||
emit partChanged(partId);
|
||||
|
||||
if (newPartAdded)
|
||||
emit partListChanged();
|
||||
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
const SkeletonEdge *SkeletonDocument::findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const
|
||||
{
|
||||
const SkeletonNode *firstNode = nullptr;
|
||||
firstNode = findNode(firstNodeId);
|
||||
if (nullptr == firstNode) {
|
||||
qDebug() << "Find node failed:" << firstNodeId;
|
||||
return nullptr;
|
||||
}
|
||||
for (auto edgeIdIt = firstNode->edgeIds.begin(); edgeIdIt != firstNode->edgeIds.end(); edgeIdIt++) {
|
||||
auto edgeIt = edgeMap.find(*edgeIdIt);
|
||||
if (edgeIt == edgeMap.end()) {
|
||||
qDebug() << "Find edge failed:" << *edgeIdIt;
|
||||
continue;
|
||||
}
|
||||
if (std::find(edgeIt->second.nodeIds.begin(), edgeIt->second.nodeIds.end(), secondNodeId) != edgeIt->second.nodeIds.end())
|
||||
return &edgeIt->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SkeletonDocument::addEdge(QUuid fromNodeId, QUuid toNodeId)
|
||||
{
|
||||
const SkeletonNode *fromNode = nullptr;
|
||||
const SkeletonNode *toNode = nullptr;
|
||||
bool toPartRemoved = false;
|
||||
|
||||
fromNode = findNode(fromNodeId);
|
||||
if (nullptr == fromNode) {
|
||||
qDebug() << "Find node failed:" << fromNodeId;
|
||||
return;
|
||||
}
|
||||
|
||||
toNode = findNode(toNodeId);
|
||||
if (nullptr == toNode) {
|
||||
qDebug() << "Find node failed:" << toNodeId;
|
||||
return;
|
||||
}
|
||||
|
||||
QUuid toPartId = toNode->partId;
|
||||
|
||||
auto fromPart = partMap.find(fromNode->partId);
|
||||
if (fromPart == partMap.end()) {
|
||||
qDebug() << "Find part failed:" << fromNode->partId;
|
||||
return;
|
||||
}
|
||||
|
||||
if (fromNode->partId != toNode->partId) {
|
||||
toPartRemoved = true;
|
||||
std::vector<QUuid> toGroup;
|
||||
std::set<QUuid> visitMap;
|
||||
joinNodeAndNeiborsToGroup(&toGroup, toNodeId, &visitMap);
|
||||
for (auto nodeIdIt = toGroup.begin(); nodeIdIt != toGroup.end(); nodeIdIt++) {
|
||||
auto nodeIt = nodeMap.find(*nodeIdIt);
|
||||
if (nodeIt == nodeMap.end()) {
|
||||
qDebug() << "Find node failed:" << *nodeIdIt;
|
||||
continue;
|
||||
}
|
||||
nodeIt->second.partId = fromNode->partId;
|
||||
fromPart->second.nodeIds.push_back(nodeIt->first);
|
||||
for (auto edgeIdIt = nodeIt->second.edgeIds.begin(); edgeIdIt != nodeIt->second.edgeIds.end(); edgeIdIt++) {
|
||||
auto edgeIt = edgeMap.find(*edgeIdIt);
|
||||
if (edgeIt == edgeMap.end()) {
|
||||
qDebug() << "Find edge failed:" << *edgeIdIt;
|
||||
continue;
|
||||
}
|
||||
edgeIt->second.partId = fromNode->partId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SkeletonEdge edge;
|
||||
edge.partId = fromNode->partId;
|
||||
edge.nodeIds.push_back(fromNode->id);
|
||||
edge.nodeIds.push_back(toNodeId);
|
||||
edgeMap[edge.id] = edge;
|
||||
|
||||
nodeMap[toNodeId].edgeIds.push_back(edge.id);
|
||||
nodeMap[fromNode->id].edgeIds.push_back(edge.id);
|
||||
|
||||
emit edgeAdded(edge.id);
|
||||
|
||||
if (toPartRemoved) {
|
||||
partIds.erase(std::remove(partIds.begin(), partIds.end(), toPartId), partIds.end());
|
||||
partMap.erase(toPartId);
|
||||
emit partRemoved(toPartId);
|
||||
emit partListChanged();
|
||||
}
|
||||
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
const SkeletonNode *SkeletonDocument::findNode(QUuid nodeId) const
|
||||
{
|
||||
auto it = nodeMap.find(nodeId);
|
||||
if (it == nodeMap.end())
|
||||
return nullptr;
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
const SkeletonEdge *SkeletonDocument::findEdge(QUuid edgeId) const
|
||||
{
|
||||
auto it = edgeMap.find(edgeId);
|
||||
if (it == edgeMap.end())
|
||||
return nullptr;
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
const SkeletonPart *SkeletonDocument::findPart(QUuid partId) const
|
||||
{
|
||||
auto it = partMap.find(partId);
|
||||
if (it == partMap.end())
|
||||
return nullptr;
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
void SkeletonDocument::scaleNodeByAddRadius(QUuid nodeId, float amount)
|
||||
{
|
||||
auto it = nodeMap.find(nodeId);
|
||||
if (it == nodeMap.end()) {
|
||||
qDebug() << "Find node failed:" << nodeId;
|
||||
return;
|
||||
}
|
||||
it->second.radius += amount;
|
||||
emit nodeRadiusChanged(nodeId);
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void SkeletonDocument::moveNodeBy(QUuid nodeId, float x, float y, float z)
|
||||
{
|
||||
auto it = nodeMap.find(nodeId);
|
||||
if (it == nodeMap.end()) {
|
||||
qDebug() << "Find node failed:" << nodeId;
|
||||
return;
|
||||
}
|
||||
it->second.x += x;
|
||||
it->second.y += y;
|
||||
it->second.z += z;
|
||||
emit nodeOriginChanged(nodeId);
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void SkeletonDocument::setNodeOrigin(QUuid nodeId, float x, float y, float z)
|
||||
{
|
||||
auto it = nodeMap.find(nodeId);
|
||||
if (it == nodeMap.end()) {
|
||||
qDebug() << "Find node failed:" << nodeId;
|
||||
return;
|
||||
}
|
||||
it->second.x = x;
|
||||
it->second.y = y;
|
||||
it->second.z = z;
|
||||
emit nodeOriginChanged(nodeId);
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void SkeletonDocument::setNodeRadius(QUuid nodeId, float radius)
|
||||
{
|
||||
auto it = nodeMap.find(nodeId);
|
||||
if (it == nodeMap.end()) {
|
||||
qDebug() << "Find node failed:" << nodeId;
|
||||
return;
|
||||
}
|
||||
it->second.radius = radius;
|
||||
emit nodeRadiusChanged(nodeId);
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void SkeletonDocument::updateTurnaround(const QImage &image)
|
||||
{
|
||||
turnaround = image;
|
||||
emit turnaroundChanged();
|
||||
}
|
||||
|
||||
void SkeletonDocument::setEditMode(SkeletonDocumentEditMode mode)
|
||||
{
|
||||
if (editMode == mode)
|
||||
return;
|
||||
|
||||
editMode = mode;
|
||||
emit editModeChanged();
|
||||
}
|
||||
|
||||
void SkeletonDocument::joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId)
|
||||
{
|
||||
if (nodeId.isNull() || visitMap->find(nodeId) != visitMap->end())
|
||||
return;
|
||||
const SkeletonNode *node = findNode(nodeId);
|
||||
if (nullptr == node) {
|
||||
qDebug() << "Find node failed:" << nodeId;
|
||||
return;
|
||||
}
|
||||
visitMap->insert(nodeId);
|
||||
group->push_back(nodeId);
|
||||
for (auto edgeIt = node->edgeIds.begin(); edgeIt != node->edgeIds.end(); edgeIt++) {
|
||||
if (noUseEdgeId == *edgeIt)
|
||||
continue;
|
||||
const SkeletonEdge *edge = findEdge(*edgeIt);
|
||||
if (nullptr == edge) {
|
||||
qDebug() << "Find edge failed:" << *edgeIt;
|
||||
continue;
|
||||
}
|
||||
for (auto nodeIt = edge->nodeIds.begin(); nodeIt != edge->nodeIds.end(); nodeIt++) {
|
||||
joinNodeAndNeiborsToGroup(group, *nodeIt, visitMap, noUseEdgeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonDocument::splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId)
|
||||
{
|
||||
const SkeletonNode *node = findNode(nodeId);
|
||||
std::set<QUuid> visitMap;
|
||||
for (auto edgeIt = node->edgeIds.begin(); edgeIt != node->edgeIds.end(); edgeIt++) {
|
||||
std::vector<QUuid> group;
|
||||
const SkeletonEdge *edge = findEdge(*edgeIt);
|
||||
if (nullptr == edge) {
|
||||
qDebug() << "Find edge failed:" << *edgeIt;
|
||||
continue;
|
||||
}
|
||||
joinNodeAndNeiborsToGroup(&group, edge->neighborOf(nodeId), &visitMap, *edgeIt);
|
||||
if (!group.empty())
|
||||
groups->push_back(group);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonDocument::splitPartByEdge(std::vector<std::vector<QUuid>> *groups, QUuid edgeId)
|
||||
{
|
||||
const SkeletonEdge *edge = findEdge(edgeId);
|
||||
if (nullptr == edge) {
|
||||
qDebug() << "Find edge failed:" << edgeId;
|
||||
return;
|
||||
}
|
||||
std::set<QUuid> visitMap;
|
||||
for (auto nodeIt = edge->nodeIds.begin(); nodeIt != edge->nodeIds.end(); nodeIt++) {
|
||||
std::vector<QUuid> group;
|
||||
joinNodeAndNeiborsToGroup(&group, *nodeIt, &visitMap, edgeId);
|
||||
if (!group.empty())
|
||||
groups->push_back(group);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonDocument::setEdgeBranchMode(QUuid edgeId, SkeletonEdgeBranchMode mode)
|
||||
{
|
||||
auto edgeIt = edgeMap.find(edgeId);
|
||||
if (edgeIt == edgeMap.end()) {
|
||||
qDebug() << "Find edge failed:" << edgeId;
|
||||
return;
|
||||
}
|
||||
edgeIt->second.branchMode = mode;
|
||||
}
|
||||
|
||||
void SkeletonDocument::setNodeRootMarkMode(QUuid nodeId, SkeletonNodeRootMarkMode mode)
|
||||
{
|
||||
auto nodeIt = nodeMap.find(nodeId);
|
||||
if (nodeIt == nodeMap.end()) {
|
||||
qDebug() << "Find node failed:" << nodeId;
|
||||
return;
|
||||
}
|
||||
nodeIt->second.rootMarkMode = mode;
|
||||
}
|
||||
|
||||
void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot)
|
||||
{
|
||||
for (const auto &partIt : partMap) {
|
||||
std::map<QString, QString> part;
|
||||
part["id"] = partIt.second.id.toString();
|
||||
part["visible"] = partIt.second.visible ? "true" : "false";
|
||||
if (!partIt.second.name.isEmpty())
|
||||
part["name"] = partIt.second.name;
|
||||
snapshot->parts[part["id"]] = part;
|
||||
}
|
||||
for (const auto &nodeIt: nodeMap) {
|
||||
std::map<QString, QString> node;
|
||||
node["id"] = nodeIt.second.id.toString();
|
||||
node["radius"] = QString::number(nodeIt.second.radius);
|
||||
node["x"] = QString::number(nodeIt.second.x);
|
||||
node["y"] = QString::number(nodeIt.second.y);
|
||||
node["z"] = QString::number(nodeIt.second.z);
|
||||
node["rootMarkMode"] = SkeletonNodeRootMarkModeToString(nodeIt.second.rootMarkMode);
|
||||
node["partId"] = nodeIt.second.partId.toString();
|
||||
if (!nodeIt.second.name.isEmpty())
|
||||
node["name"] = nodeIt.second.name;
|
||||
snapshot->nodes[node["id"]] = node;
|
||||
qDebug() << "Export node to snapshot " << node["id"] << node["x"] << node["y"] << node["z"];
|
||||
}
|
||||
for (const auto &edgeIt: edgeMap) {
|
||||
if (edgeIt.second.nodeIds.size() != 2)
|
||||
continue;
|
||||
std::map<QString, QString> edge;
|
||||
edge["id"] = edgeIt.second.id.toString();
|
||||
edge["from"] = edgeIt.second.nodeIds[0].toString();
|
||||
edge["to"] = edgeIt.second.nodeIds[1].toString();
|
||||
edge["branchMode"] = SkeletonEdgeBranchModeToString(edgeIt.second.branchMode);
|
||||
edge["partId"] = edgeIt.second.partId.toString();
|
||||
if (!edgeIt.second.name.isEmpty())
|
||||
edge["name"] = edgeIt.second.name;
|
||||
snapshot->edges[edge["id"]] = edge;
|
||||
qDebug() << "Export edge to snapshot " << edge["from"] << "<=>" << edge["to"];
|
||||
}
|
||||
for (const auto &partIdIt: partIds) {
|
||||
snapshot->partIdList.push_back(partIdIt.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot)
|
||||
{
|
||||
for (const auto &nodeIt: nodeMap) {
|
||||
emit nodeRemoved(nodeIt.first);
|
||||
}
|
||||
for (const auto &edgeIt: edgeMap) {
|
||||
emit edgeRemoved(edgeIt.first);
|
||||
}
|
||||
for (const auto &partIt : partMap) {
|
||||
emit partRemoved(partIt.first);
|
||||
}
|
||||
|
||||
nodeMap.clear();
|
||||
edgeMap.clear();
|
||||
partMap.clear();
|
||||
|
||||
for (const auto &nodeKv : snapshot.nodes) {
|
||||
SkeletonNode node(QUuid(nodeKv.first));
|
||||
node.name = valueOfKeyInMapOrEmpty(nodeKv.second, "name");
|
||||
node.radius = valueOfKeyInMapOrEmpty(nodeKv.second, "radius").toFloat();
|
||||
node.x = valueOfKeyInMapOrEmpty(nodeKv.second, "x").toFloat();
|
||||
node.y = valueOfKeyInMapOrEmpty(nodeKv.second, "y").toFloat();
|
||||
node.z = valueOfKeyInMapOrEmpty(nodeKv.second, "z").toFloat();
|
||||
node.partId = QUuid(valueOfKeyInMapOrEmpty(nodeKv.second, "partId"));
|
||||
node.rootMarkMode = SkeletonNodeRootMarkModeFromString(valueOfKeyInMapOrEmpty(nodeKv.second, "rootMarkMode"));
|
||||
nodeMap[node.id] = node;
|
||||
}
|
||||
for (const auto &edgeKv : snapshot.edges) {
|
||||
SkeletonEdge edge(QUuid(edgeKv.first));
|
||||
edge.name = valueOfKeyInMapOrEmpty(edgeKv.second, "name");
|
||||
edge.partId = QUuid(valueOfKeyInMapOrEmpty(edgeKv.second, "partId"));
|
||||
edge.branchMode = SkeletonEdgeBranchModeFromString(valueOfKeyInMapOrEmpty(edgeKv.second, "branchMode"));
|
||||
QString fromNodeId = valueOfKeyInMapOrEmpty(edgeKv.second, "from");
|
||||
if (!fromNodeId.isEmpty())
|
||||
edge.nodeIds.push_back(QUuid(fromNodeId));
|
||||
QString toNodeId = valueOfKeyInMapOrEmpty(edgeKv.second, "to");
|
||||
if (!toNodeId.isEmpty())
|
||||
edge.nodeIds.push_back(QUuid(toNodeId));
|
||||
edgeMap[edge.id] = edge;
|
||||
}
|
||||
for (const auto &partKv : snapshot.parts) {
|
||||
SkeletonPart part(QUuid(partKv.first));
|
||||
part.name = valueOfKeyInMapOrEmpty(partKv.second, "name");
|
||||
part.visible = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "visible"));
|
||||
partMap[part.id] = part;
|
||||
}
|
||||
for (const auto &partIdIt: snapshot.partIdList) {
|
||||
partIds.push_back(QUuid(partIdIt));
|
||||
}
|
||||
|
||||
for (const auto &nodeIt: nodeMap) {
|
||||
emit nodeAdded(nodeIt.first);
|
||||
}
|
||||
for (const auto &edgeIt: edgeMap) {
|
||||
emit edgeAdded(edgeIt.first);
|
||||
}
|
||||
for (const auto &partIt : partMap) {
|
||||
emit partAdded(partIt.first);
|
||||
}
|
||||
|
||||
emit partListChanged();
|
||||
}
|
||||
|
||||
const char *SkeletonNodeRootMarkModeToString(SkeletonNodeRootMarkMode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case SkeletonNodeRootMarkMode::Auto:
|
||||
return "auto";
|
||||
case SkeletonNodeRootMarkMode::MarkAsRoot:
|
||||
return "markAsRoot";
|
||||
case SkeletonNodeRootMarkMode::MarkAsNotRoot:
|
||||
return "markAsNotRoot";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
SkeletonNodeRootMarkMode SkeletonNodeRootMarkModeFromString(const QString &mode)
|
||||
{
|
||||
if (mode == "auto")
|
||||
return SkeletonNodeRootMarkMode::Auto;
|
||||
if (mode == "markAsRoot")
|
||||
return SkeletonNodeRootMarkMode::MarkAsRoot;
|
||||
if (mode == "markAsNotRoot")
|
||||
return SkeletonNodeRootMarkMode::MarkAsNotRoot;
|
||||
return SkeletonNodeRootMarkMode::Auto;
|
||||
}
|
||||
|
||||
const char *SkeletonEdgeBranchModeToString(SkeletonEdgeBranchMode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case SkeletonEdgeBranchMode::Auto:
|
||||
return "auto";
|
||||
case SkeletonEdgeBranchMode::NoTrivial:
|
||||
return "noTrivial";
|
||||
case SkeletonEdgeBranchMode::Trivial:
|
||||
return "trivial";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
SkeletonEdgeBranchMode SkeletonEdgeBranchModeFromString(const QString &mode)
|
||||
{
|
||||
if (mode == "auto")
|
||||
return SkeletonEdgeBranchMode::Auto;
|
||||
if (mode == "noTrivial")
|
||||
return SkeletonEdgeBranchMode::NoTrivial;
|
||||
if (mode == "trivial")
|
||||
return SkeletonEdgeBranchMode::Trivial;
|
||||
return SkeletonEdgeBranchMode::Auto;
|
||||
}
|
||||
|
||||
Mesh *SkeletonDocument::takeResultMesh()
|
||||
{
|
||||
Mesh *resultMesh = m_resultMesh;
|
||||
m_resultMesh = nullptr;
|
||||
return resultMesh;
|
||||
}
|
||||
|
||||
void SkeletonDocument::meshReady()
|
||||
{
|
||||
Mesh *resultMesh = m_meshGenerator->takeResultMesh();
|
||||
|
||||
QImage *resultPreview = m_meshGenerator->takePreview();
|
||||
if (resultPreview) {
|
||||
preview = *resultPreview;
|
||||
delete resultPreview;
|
||||
}
|
||||
|
||||
for (auto &part: partMap) {
|
||||
QImage *resultPartPreview = m_meshGenerator->takePartPreview(part.first.toString());
|
||||
if (resultPartPreview) {
|
||||
part.second.preview = *resultPartPreview;
|
||||
emit partPreviewChanged(part.first);
|
||||
delete resultPartPreview;
|
||||
}
|
||||
}
|
||||
|
||||
delete m_resultMesh;
|
||||
m_resultMesh = resultMesh;
|
||||
|
||||
if (nullptr == m_resultMesh) {
|
||||
qDebug() << "Result mesh is null";
|
||||
}
|
||||
|
||||
delete m_meshGenerator;
|
||||
m_meshGenerator = nullptr;
|
||||
|
||||
qDebug() << "Mesh generation done";
|
||||
|
||||
emit resultMeshChanged();
|
||||
|
||||
if (m_resultMeshIsObsolete) {
|
||||
generateMesh();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonDocument::generateMesh()
|
||||
{
|
||||
if (nullptr != m_meshGenerator) {
|
||||
m_resultMeshIsObsolete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Mesh generating..";
|
||||
|
||||
m_resultMeshIsObsolete = false;
|
||||
|
||||
QThread *thread = new QThread;
|
||||
|
||||
SkeletonSnapshot *snapshot = new SkeletonSnapshot;
|
||||
toSnapshot(snapshot);
|
||||
m_meshGenerator = new MeshGenerator(snapshot, thread);
|
||||
m_meshGenerator->moveToThread(thread);
|
||||
m_meshGenerator->addPreviewRequirement();
|
||||
for (auto &part: partMap) {
|
||||
//if (part.second.previewIsObsolete) {
|
||||
// part.second.previewIsObsolete = false;
|
||||
m_meshGenerator->addPartPreviewRequirement(part.first.toString());
|
||||
//}
|
||||
}
|
||||
connect(thread, &QThread::started, m_meshGenerator, &MeshGenerator::process);
|
||||
connect(m_meshGenerator, &MeshGenerator::finished, this, &SkeletonDocument::meshReady);
|
||||
connect(m_meshGenerator, &MeshGenerator::finished, thread, &QThread::quit);
|
||||
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||
thread->start();
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
#ifndef SKELETON_DOCUMENT_H
|
||||
#define SKELETON_DOCUMENT_H
|
||||
#include <QObject>
|
||||
#include <QUuid>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <QImage>
|
||||
#include "skeletonsnapshot.h"
|
||||
#include "mesh.h"
|
||||
#include "meshgenerator.h"
|
||||
|
||||
enum class SkeletonNodeRootMarkMode
|
||||
{
|
||||
Auto = 0,
|
||||
MarkAsRoot,
|
||||
MarkAsNotRoot
|
||||
};
|
||||
|
||||
const char *SkeletonNodeRootMarkModeToString(SkeletonNodeRootMarkMode mode);
|
||||
SkeletonNodeRootMarkMode SkeletonNodeRootMarkModeFromString(const QString &mode);
|
||||
|
||||
class SkeletonNode
|
||||
{
|
||||
public:
|
||||
SkeletonNode(const QUuid &withId=QUuid()) :
|
||||
x(0),
|
||||
y(0),
|
||||
z(0),
|
||||
radius(0),
|
||||
rootMarkMode(SkeletonNodeRootMarkMode::Auto)
|
||||
{
|
||||
id = withId.isNull() ? QUuid::createUuid() : withId;
|
||||
}
|
||||
QUuid id;
|
||||
QUuid partId;
|
||||
QString name;
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float radius;
|
||||
SkeletonNodeRootMarkMode rootMarkMode;
|
||||
std::vector<QUuid> edgeIds;
|
||||
};
|
||||
|
||||
enum class SkeletonEdgeBranchMode
|
||||
{
|
||||
Auto = 0,
|
||||
Trivial,
|
||||
NoTrivial
|
||||
};
|
||||
|
||||
const char *SkeletonEdgeBranchModeToString(SkeletonEdgeBranchMode mode);
|
||||
SkeletonEdgeBranchMode SkeletonEdgeBranchModeFromString(const QString &mode);
|
||||
|
||||
class SkeletonEdge
|
||||
{
|
||||
public:
|
||||
SkeletonEdge(const QUuid &withId=QUuid()) :
|
||||
branchMode(SkeletonEdgeBranchMode::Auto)
|
||||
{
|
||||
id = withId.isNull() ? QUuid::createUuid() : withId;
|
||||
}
|
||||
QUuid id;
|
||||
QUuid partId;
|
||||
QString name;
|
||||
SkeletonEdgeBranchMode branchMode;
|
||||
std::vector<QUuid> nodeIds;
|
||||
QUuid neighborOf(QUuid nodeId) const
|
||||
{
|
||||
if (nodeIds.size() != 2)
|
||||
return QUuid();
|
||||
return nodeIds[0] == nodeId ? nodeIds[1] : nodeIds[0];
|
||||
}
|
||||
};
|
||||
|
||||
class SkeletonPart
|
||||
{
|
||||
public:
|
||||
QUuid id;
|
||||
QString name;
|
||||
bool visible;
|
||||
QImage preview;
|
||||
std::vector<QUuid> nodeIds;
|
||||
bool previewIsObsolete;
|
||||
SkeletonPart(const QUuid &withId=QUuid()) :
|
||||
visible(true),
|
||||
previewIsObsolete(true)
|
||||
{
|
||||
id = withId.isNull() ? QUuid::createUuid() : withId;
|
||||
}
|
||||
};
|
||||
|
||||
class SkeletonHistoryItem
|
||||
{
|
||||
public:
|
||||
SkeletonSnapshot snapshot;
|
||||
QImage preview;
|
||||
};
|
||||
|
||||
enum class SkeletonDocumentEditMode
|
||||
{
|
||||
Add = 0,
|
||||
Select,
|
||||
Drag,
|
||||
ZoomIn,
|
||||
ZoomOut
|
||||
};
|
||||
|
||||
enum class SkeletonProfile
|
||||
{
|
||||
Unknown = 0,
|
||||
Main,
|
||||
Side
|
||||
};
|
||||
|
||||
class SkeletonDocument : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void partAdded(QUuid partId);
|
||||
void nodeAdded(QUuid nodeId);
|
||||
void edgeAdded(QUuid edgeId);
|
||||
void partRemoved(QUuid partId);
|
||||
void partListChanged();
|
||||
void nodeRemoved(QUuid nodeId);
|
||||
void edgeRemoved(QUuid edgeId);
|
||||
void nodeRadiusChanged(QUuid nodeId);
|
||||
void nodeOriginChanged(QUuid nodeId);
|
||||
void edgeChanged(QUuid edgeId);
|
||||
void partChanged(QUuid partId);
|
||||
void partPreviewChanged(QUuid partId);
|
||||
void resultMeshChanged();
|
||||
void turnaroundChanged();
|
||||
void editModeChanged();
|
||||
void skeletonChanged();
|
||||
public:
|
||||
SkeletonDocument();
|
||||
~SkeletonDocument();
|
||||
std::map<QUuid, SkeletonPart> partMap;
|
||||
std::map<QUuid, SkeletonNode> nodeMap;
|
||||
std::map<QUuid, SkeletonEdge> edgeMap;
|
||||
std::vector<SkeletonHistoryItem> historyItems;
|
||||
std::vector<QUuid> partIds;
|
||||
QImage turnaround;
|
||||
SkeletonDocumentEditMode editMode;
|
||||
void toSnapshot(SkeletonSnapshot *snapshot);
|
||||
void fromSnapshot(const SkeletonSnapshot &snapshot);
|
||||
const SkeletonNode *findNode(QUuid nodeId) const;
|
||||
const SkeletonEdge *findEdge(QUuid edgeId) const;
|
||||
const SkeletonPart *findPart(QUuid partId) const;
|
||||
const SkeletonEdge *findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const;
|
||||
Mesh *takeResultMesh();
|
||||
QImage preview;
|
||||
void updateTurnaround(const QImage &image);
|
||||
public slots:
|
||||
void removeNode(QUuid nodeId);
|
||||
void removeEdge(QUuid edgeId);
|
||||
void removePart(QUuid partId);
|
||||
void showPart(QUuid partId);
|
||||
void hidePart(QUuid partId);
|
||||
void addNode(float x, float y, float z, float radius, QUuid fromNodeId);
|
||||
void scaleNodeByAddRadius(QUuid nodeId, float amount);
|
||||
void moveNodeBy(QUuid nodeId, float x, float y, float z);
|
||||
void setNodeOrigin(QUuid nodeId, float x, float y, float z);
|
||||
void setNodeRadius(QUuid nodeId, float radius);
|
||||
void addEdge(QUuid fromNodeId, QUuid toNodeId);
|
||||
void setEditMode(SkeletonDocumentEditMode mode);
|
||||
void setEdgeBranchMode(QUuid edgeId, SkeletonEdgeBranchMode mode);
|
||||
void setNodeRootMarkMode(QUuid nodeId, SkeletonNodeRootMarkMode mode);
|
||||
void uiReady();
|
||||
void generateMesh();
|
||||
void meshReady();
|
||||
private:
|
||||
void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId);
|
||||
void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid());
|
||||
void splitPartByEdge(std::vector<std::vector<QUuid>> *groups, QUuid edgeId);
|
||||
private:
|
||||
bool m_resultMeshIsObsolete;
|
||||
MeshGenerator *m_meshGenerator;
|
||||
Mesh *m_resultMesh;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,256 @@
|
|||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QGridLayout>
|
||||
#include <QToolBar>
|
||||
#include <QPushButton>
|
||||
#include <QFileDialog>
|
||||
#include <QTabWidget>
|
||||
#include "skeletondocumentwindow.h"
|
||||
#include "skeletongraphicswidget.h"
|
||||
#include "skeletonpartlistwidget.h"
|
||||
#include "skeletonedgepropertywidget.h"
|
||||
#include "skeletonnodepropertywidget.h"
|
||||
#include "theme.h"
|
||||
|
||||
SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||
m_document(nullptr),
|
||||
m_firstShow(true)
|
||||
{
|
||||
m_document = new SkeletonDocument;
|
||||
|
||||
QVBoxLayout *toolButtonLayout = new QVBoxLayout;
|
||||
toolButtonLayout->setSpacing(0);
|
||||
toolButtonLayout->setContentsMargins(5, 10, 4, 0);
|
||||
|
||||
QPushButton *undoButton = new QPushButton(QChar(fa::undo));
|
||||
undoButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
|
||||
undoButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
|
||||
|
||||
QPushButton *addButton = new QPushButton(QChar(fa::plus));
|
||||
addButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
|
||||
addButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
|
||||
|
||||
QPushButton *selectButton = new QPushButton(QChar(fa::mousepointer));
|
||||
selectButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
|
||||
selectButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
|
||||
|
||||
QPushButton *dragButton = new QPushButton(QChar(fa::handrocko));
|
||||
dragButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
|
||||
dragButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
|
||||
|
||||
QPushButton *zoomInButton = new QPushButton(QChar(fa::searchplus));
|
||||
zoomInButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
|
||||
zoomInButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
|
||||
|
||||
QPushButton *zoomOutButton = new QPushButton(QChar(fa::searchminus));
|
||||
zoomOutButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
|
||||
zoomOutButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
|
||||
|
||||
QPushButton *changeTurnaroundButton = new QPushButton(QChar(fa::image));
|
||||
changeTurnaroundButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
|
||||
changeTurnaroundButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
|
||||
|
||||
QPushButton *resetButton = new QPushButton(QChar(fa::trash));
|
||||
resetButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
|
||||
resetButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
|
||||
|
||||
toolButtonLayout->addWidget(undoButton);
|
||||
toolButtonLayout->addSpacing(10);
|
||||
toolButtonLayout->addWidget(addButton);
|
||||
toolButtonLayout->addWidget(selectButton);
|
||||
toolButtonLayout->addWidget(dragButton);
|
||||
toolButtonLayout->addWidget(zoomInButton);
|
||||
toolButtonLayout->addWidget(zoomOutButton);
|
||||
toolButtonLayout->addSpacing(10);
|
||||
toolButtonLayout->addWidget(changeTurnaroundButton);
|
||||
toolButtonLayout->addSpacing(30);
|
||||
toolButtonLayout->addWidget(resetButton);
|
||||
|
||||
QLabel *dust3dJezzasoftLabel = new QLabel;
|
||||
QImage dust3dJezzasoftImage;
|
||||
dust3dJezzasoftImage.load(":/resources/dust3d_jezzasoft.png");
|
||||
dust3dJezzasoftLabel->setPixmap(QPixmap::fromImage(dust3dJezzasoftImage));
|
||||
|
||||
QHBoxLayout *logoLayout = new QHBoxLayout;
|
||||
logoLayout->addWidget(dust3dJezzasoftLabel);
|
||||
logoLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
QVBoxLayout *mainLeftLayout = new QVBoxLayout;
|
||||
mainLeftLayout->setSpacing(0);
|
||||
mainLeftLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLeftLayout->addLayout(toolButtonLayout);
|
||||
mainLeftLayout->addStretch();
|
||||
mainLeftLayout->addLayout(logoLayout);
|
||||
mainLeftLayout->addSpacing(10);
|
||||
|
||||
SkeletonGraphicsWidget *graphicsWidget = new SkeletonGraphicsWidget(m_document);
|
||||
|
||||
SkeletonGraphicsContainerWidget *containerWidget = new SkeletonGraphicsContainerWidget;
|
||||
|
||||
containerWidget->setGraphicsWidget(graphicsWidget);
|
||||
QGridLayout *containerLayout = new QGridLayout;
|
||||
containerLayout->setSpacing(0);
|
||||
containerLayout->setContentsMargins(1, 0, 0, 0);
|
||||
containerLayout->addWidget(graphicsWidget);
|
||||
containerWidget->setLayout(containerLayout);
|
||||
containerWidget->setMinimumSize(400, 400);
|
||||
|
||||
m_modelWidget = new ModelWidget(containerWidget);
|
||||
m_modelWidget->setMinimumSize(128, 128);
|
||||
m_modelWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
m_modelWidget->move(10, 10);
|
||||
m_modelWidget->setGraphicsFunctions(graphicsWidget);
|
||||
|
||||
SkeletonPartListWidget *partListWidget = new SkeletonPartListWidget(m_document);
|
||||
|
||||
QTabWidget *firstTabWidget = new QTabWidget;
|
||||
firstTabWidget->setFont(Theme::awesome()->font(Theme::toolIconFontSize * 3 / 4));
|
||||
firstTabWidget->setMaximumWidth(200);
|
||||
firstTabWidget->addTab(partListWidget, QChar(fa::puzzlepiece));
|
||||
firstTabWidget->addTab(new QWidget, QChar(fa::history));
|
||||
firstTabWidget->addTab(new QWidget, QChar(fa::wrench));
|
||||
|
||||
SkeletonEdgePropertyWidget *edgePropertyWidget = new SkeletonEdgePropertyWidget(m_document);
|
||||
SkeletonNodePropertyWidget *nodePropertyWidget = new SkeletonNodePropertyWidget(m_document);
|
||||
|
||||
QVBoxLayout *propertyLayout = new QVBoxLayout;
|
||||
propertyLayout->addWidget(edgePropertyWidget);
|
||||
propertyLayout->addWidget(nodePropertyWidget);
|
||||
|
||||
QWidget *propertyWidget = new QWidget;
|
||||
propertyWidget->setLayout(propertyLayout);
|
||||
|
||||
QTabWidget *secondTabWidget = new QTabWidget;
|
||||
secondTabWidget->setFont(Theme::awesome()->font(Theme::toolIconFontSize * 3 / 4));
|
||||
secondTabWidget->setMaximumWidth(200);
|
||||
secondTabWidget->setMaximumHeight(90);
|
||||
secondTabWidget->addTab(propertyWidget, QChar(fa::adjust));
|
||||
|
||||
QVBoxLayout *mainRightLayout = new QVBoxLayout;
|
||||
mainRightLayout->setSpacing(0);
|
||||
mainRightLayout->setContentsMargins(5, 5, 5, 5);
|
||||
mainRightLayout->addWidget(firstTabWidget);
|
||||
mainRightLayout->addWidget(secondTabWidget);
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
mainLayout->setSpacing(0);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->addLayout(mainLeftLayout);
|
||||
mainLayout->addWidget(containerWidget);
|
||||
mainLayout->addLayout(mainRightLayout);
|
||||
|
||||
QWidget *centralWidget = new QWidget;
|
||||
centralWidget->setLayout(mainLayout);
|
||||
|
||||
setCentralWidget(centralWidget);
|
||||
setWindowTitle(tr("Dust3D"));
|
||||
|
||||
connect(containerWidget, &SkeletonGraphicsContainerWidget::containerSizeChanged,
|
||||
graphicsWidget, &SkeletonGraphicsWidget::canvasResized);
|
||||
|
||||
connect(m_document, &SkeletonDocument::turnaroundChanged,
|
||||
graphicsWidget, &SkeletonGraphicsWidget::turnaroundChanged);
|
||||
|
||||
connect(changeTurnaroundButton, &QPushButton::clicked, this, &SkeletonDocumentWindow::changeTurnaround);
|
||||
|
||||
connect(addButton, &QPushButton::clicked, [=]() {
|
||||
m_document->setEditMode(SkeletonDocumentEditMode::Add);
|
||||
});
|
||||
|
||||
connect(selectButton, &QPushButton::clicked, [=]() {
|
||||
m_document->setEditMode(SkeletonDocumentEditMode::Select);
|
||||
});
|
||||
|
||||
connect(dragButton, &QPushButton::clicked, [=]() {
|
||||
m_document->setEditMode(SkeletonDocumentEditMode::Drag);
|
||||
});
|
||||
|
||||
connect(zoomInButton, &QPushButton::clicked, [=]() {
|
||||
m_document->setEditMode(SkeletonDocumentEditMode::ZoomIn);
|
||||
});
|
||||
|
||||
connect(zoomOutButton, &QPushButton::clicked, [=]() {
|
||||
m_document->setEditMode(SkeletonDocumentEditMode::ZoomOut);
|
||||
});
|
||||
|
||||
connect(m_document, &SkeletonDocument::editModeChanged, graphicsWidget, &SkeletonGraphicsWidget::editModeChanged);
|
||||
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::addNode, m_document, &SkeletonDocument::addNode);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::scaleNodeByAddRadius, m_document, &SkeletonDocument::scaleNodeByAddRadius);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::moveNodeBy, m_document, &SkeletonDocument::moveNodeBy);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::removeNode, m_document, &SkeletonDocument::removeNode);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::setEditMode, m_document, &SkeletonDocument::setEditMode);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::removeEdge, m_document, &SkeletonDocument::removeEdge);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::addEdge, m_document, &SkeletonDocument::addEdge);
|
||||
|
||||
connect(m_document, &SkeletonDocument::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded);
|
||||
connect(m_document, &SkeletonDocument::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved);
|
||||
connect(m_document, &SkeletonDocument::edgeAdded, graphicsWidget, &SkeletonGraphicsWidget::edgeAdded);
|
||||
connect(m_document, &SkeletonDocument::edgeRemoved, graphicsWidget, &SkeletonGraphicsWidget::edgeRemoved);
|
||||
connect(m_document, &SkeletonDocument::nodeRadiusChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeRadiusChanged);
|
||||
connect(m_document, &SkeletonDocument::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged);
|
||||
|
||||
connect(m_document, &SkeletonDocument::partListChanged, partListWidget, &SkeletonPartListWidget::partListChanged);
|
||||
connect(m_document, &SkeletonDocument::partPreviewChanged, partListWidget, &SkeletonPartListWidget::partPreviewChanged);
|
||||
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::checkEdge, edgePropertyWidget, &SkeletonEdgePropertyWidget::showProperties);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::uncheckEdge, [=](QUuid edgeId) {
|
||||
edgePropertyWidget->hide();
|
||||
});
|
||||
connect(m_document, &SkeletonDocument::edgeRemoved, [=](QUuid edgeId) {
|
||||
if (edgeId == edgePropertyWidget->currentEdgeId()) {
|
||||
edgePropertyWidget->hide();
|
||||
}
|
||||
});
|
||||
connect(edgePropertyWidget, &SkeletonEdgePropertyWidget::setEdgeBranchMode, m_document, &SkeletonDocument::setEdgeBranchMode);
|
||||
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::checkNode, nodePropertyWidget, &SkeletonNodePropertyWidget::showProperties);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::uncheckNode, [=](QUuid nodeId) {
|
||||
nodePropertyWidget->hide();
|
||||
});
|
||||
connect(m_document, &SkeletonDocument::nodeRemoved, [=](QUuid nodeId) {
|
||||
if (nodeId == nodePropertyWidget->currentNodeId()) {
|
||||
nodePropertyWidget->hide();
|
||||
}
|
||||
});
|
||||
connect(nodePropertyWidget, &SkeletonNodePropertyWidget::setNodeRootMarkMode, m_document, &SkeletonDocument::setNodeRootMarkMode);
|
||||
|
||||
connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh);
|
||||
|
||||
connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
|
||||
m_modelWidget->updateMesh(m_document->takeResultMesh());
|
||||
});
|
||||
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::cursorChanged, [=]() {
|
||||
m_modelWidget->setCursor(graphicsWidget->cursor());
|
||||
});
|
||||
|
||||
connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady);
|
||||
}
|
||||
|
||||
SkeletonDocumentWindow::~SkeletonDocumentWindow()
|
||||
{
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::showEvent(QShowEvent *event)
|
||||
{
|
||||
QMainWindow::showEvent(event);
|
||||
if (m_firstShow) {
|
||||
m_firstShow = false;
|
||||
emit initialized();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::changeTurnaround()
|
||||
{
|
||||
QString fileName = QFileDialog::getOpenFileName(this, tr("Open Turnaround Reference Image"),
|
||||
QString(),
|
||||
tr("Image Files (*.png *.jpg *.bmp)")).trimmed();
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
QImage image;
|
||||
if (!image.load(fileName))
|
||||
return;
|
||||
m_document->updateTurnaround(image);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef SKELETON_DOCUMENT_WINDOW_H
|
||||
#define SKELETON_DOCUMENT_WINDOW_H
|
||||
#include <QMainWindow>
|
||||
#include <QShowEvent>
|
||||
#include "skeletondocument.h"
|
||||
#include "modelwidget.h"
|
||||
|
||||
class SkeletonDocumentWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void initialized();
|
||||
public:
|
||||
SkeletonDocumentWindow();
|
||||
~SkeletonDocumentWindow();
|
||||
protected:
|
||||
void showEvent(QShowEvent *event);
|
||||
public slots:
|
||||
void changeTurnaround();
|
||||
private:
|
||||
SkeletonDocument *m_document;
|
||||
bool m_firstShow;
|
||||
ModelWidget *m_modelWidget;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
#include <QFormLayout>
|
||||
#include <QObject>
|
||||
#include <QComboBox>
|
||||
#include <QtGlobal>
|
||||
#include "skeletonedgepropertywidget.h"
|
||||
|
||||
SkeletonEdgePropertyWidget::SkeletonEdgePropertyWidget(const SkeletonDocument *document) :
|
||||
m_branchModeComboBox(nullptr),
|
||||
m_document(document)
|
||||
{
|
||||
m_branchModeComboBox = new QComboBox;
|
||||
|
||||
m_branchModeComboBox->insertItem(0, tr("Auto"), (int)SkeletonEdgeBranchMode::Auto);
|
||||
m_branchModeComboBox->insertItem(1, tr("No Trivial"), (int)SkeletonEdgeBranchMode::NoTrivial);
|
||||
m_branchModeComboBox->insertItem(2, tr("Trivial"), (int)SkeletonEdgeBranchMode::Trivial);
|
||||
|
||||
QFormLayout *formLayout = new QFormLayout;
|
||||
formLayout->addRow(tr("Joint Branch Mode:"), m_branchModeComboBox);
|
||||
|
||||
setLayout(formLayout);
|
||||
|
||||
hide();
|
||||
|
||||
connect(m_branchModeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [=](int index) {
|
||||
if (-1 != index) {
|
||||
int mode = m_branchModeComboBox->itemData(index).toInt();
|
||||
emit setEdgeBranchMode(m_edgeId, static_cast<SkeletonEdgeBranchMode>(mode));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SkeletonEdgePropertyWidget::showProperties(QUuid edgeId)
|
||||
{
|
||||
m_edgeId = edgeId;
|
||||
updateData();
|
||||
show();
|
||||
}
|
||||
|
||||
void SkeletonEdgePropertyWidget::updateData()
|
||||
{
|
||||
const SkeletonEdge *edge = m_document->findEdge(m_edgeId);
|
||||
if (nullptr == edge) {
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
int selectIndex = m_branchModeComboBox->findData((int)edge->branchMode);
|
||||
m_branchModeComboBox->setCurrentIndex(selectIndex);
|
||||
}
|
||||
|
||||
QUuid SkeletonEdgePropertyWidget::currentEdgeId()
|
||||
{
|
||||
return m_edgeId;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef SKELETON_EDGE_PROPERTY_WIDGET_H
|
||||
#define SKELETON_EDGE_PROPERTY_WIDGET_H
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
#include <QObject>
|
||||
#include "skeletondocument.h"
|
||||
|
||||
class SkeletonEdgePropertyWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void setEdgeBranchMode(QUuid edgeId, SkeletonEdgeBranchMode mode);
|
||||
public slots:
|
||||
void showProperties(QUuid edgeId);
|
||||
public:
|
||||
SkeletonEdgePropertyWidget(const SkeletonDocument *document);
|
||||
QUuid currentEdgeId();
|
||||
private:
|
||||
void updateData();
|
||||
private:
|
||||
QComboBox *m_branchModeComboBox;
|
||||
const SkeletonDocument *m_document;
|
||||
QUuid m_edgeId;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,74 +0,0 @@
|
|||
#include <QPen>
|
||||
#include "skeletoneditedgeitem.h"
|
||||
#include "theme.h"
|
||||
|
||||
SkeletonEditEdgeItem::SkeletonEditEdgeItem(QGraphicsItem *parent) :
|
||||
QGraphicsLineItem(parent),
|
||||
m_firstNode(NULL),
|
||||
m_secondNode(NULL)
|
||||
{
|
||||
setData(0, "edge");
|
||||
m_checked = false;
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
void SkeletonEditEdgeItem::setChecked(bool checked)
|
||||
{
|
||||
if (m_checked == checked) {
|
||||
return;
|
||||
}
|
||||
m_checked = checked;
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
bool SkeletonEditEdgeItem::checked()
|
||||
{
|
||||
return m_checked;
|
||||
}
|
||||
|
||||
void SkeletonEditEdgeItem::updateAppearance()
|
||||
{
|
||||
QPen pen;
|
||||
if (m_firstNode) {
|
||||
QColor color = m_firstNode->sideColor();
|
||||
color.setAlphaF(Theme::edgeAlpha);
|
||||
pen.setColor(color);
|
||||
} else if (m_secondNode) {
|
||||
QColor color = m_secondNode->sideColor();
|
||||
color.setAlphaF(Theme::edgeAlpha);
|
||||
pen.setColor(color);
|
||||
}
|
||||
pen.setWidth(Theme::skeletonEdgeWidth);
|
||||
setPen(pen);
|
||||
}
|
||||
|
||||
void SkeletonEditEdgeItem::setNodes(SkeletonEditNodeItem *first, SkeletonEditNodeItem *second)
|
||||
{
|
||||
m_firstNode = first;
|
||||
m_secondNode = second;
|
||||
updatePosition();
|
||||
}
|
||||
|
||||
bool SkeletonEditEdgeItem::connects(SkeletonEditNodeItem *nodeItem)
|
||||
{
|
||||
return m_firstNode == nodeItem || m_secondNode == nodeItem;
|
||||
}
|
||||
|
||||
void SkeletonEditEdgeItem::updatePosition()
|
||||
{
|
||||
if (m_firstNode && m_secondNode) {
|
||||
QLineF line(m_firstNode->origin(), m_secondNode->origin());
|
||||
setLine(line);
|
||||
}
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
SkeletonEditNodeItem *SkeletonEditEdgeItem::firstNode()
|
||||
{
|
||||
return m_firstNode;
|
||||
}
|
||||
|
||||
SkeletonEditNodeItem *SkeletonEditEdgeItem::secondNode()
|
||||
{
|
||||
return m_secondNode;
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
#ifndef SKELETON_EDIT_EDGE_ITEM_H
|
||||
#define SKELETON_EDIT_EDGE_ITEM_H
|
||||
#include <QGraphicsEllipseItem>
|
||||
#include "skeletoneditnodeitem.h"
|
||||
|
||||
class SkeletonEditEdgeItem : public QGraphicsLineItem
|
||||
{
|
||||
public:
|
||||
SkeletonEditEdgeItem(QGraphicsItem *parent = 0);
|
||||
void setNodes(SkeletonEditNodeItem *first, SkeletonEditNodeItem *second);
|
||||
void updatePosition();
|
||||
SkeletonEditNodeItem *firstNode();
|
||||
SkeletonEditNodeItem *secondNode();
|
||||
bool connects(SkeletonEditNodeItem *nodeItem);
|
||||
void setChecked(bool checked);
|
||||
bool checked();
|
||||
private:
|
||||
SkeletonEditNodeItem *m_firstNode;
|
||||
SkeletonEditNodeItem *m_secondNode;
|
||||
bool m_checked;
|
||||
private:
|
||||
void updateAppearance();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,713 +0,0 @@
|
|||
#include <QGraphicsPixmapItem>
|
||||
#include <QXmlStreamWriter>
|
||||
#include <QFile>
|
||||
#include <QApplication>
|
||||
#include <QGuiApplication>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
#include "skeletoneditgraphicsview.h"
|
||||
#include "skeletoneditnodeitem.h"
|
||||
#include "skeletoneditedgeitem.h"
|
||||
#include "theme.h"
|
||||
|
||||
qreal SkeletonEditGraphicsView::m_initialNodeSize = 128;
|
||||
qreal SkeletonEditGraphicsView::m_minimalNodeSize = 8;
|
||||
|
||||
SkeletonEditGraphicsView::SkeletonEditGraphicsView(QWidget *parent) :
|
||||
QGraphicsView(parent),
|
||||
m_pendingNodeItem(NULL),
|
||||
m_pendingEdgeItem(NULL),
|
||||
m_inAddNodeMode(true),
|
||||
m_nextStartNodeItem(NULL),
|
||||
m_lastHoverNodeItem(NULL),
|
||||
m_lastMousePos(0, 0),
|
||||
m_isMovingNodeItem(false),
|
||||
m_backgroundLoaded(false),
|
||||
m_modelWidget(NULL),
|
||||
m_modelWidgetProxy(NULL),
|
||||
m_combineEnabled(true),
|
||||
m_unionEnabled(true),
|
||||
m_subdivEnabled(false)
|
||||
{
|
||||
setScene(new QGraphicsScene());
|
||||
|
||||
m_modelWidget = new ModelWidget(this);
|
||||
m_modelWidget->setMinimumSize(128, 128);
|
||||
m_modelWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
m_modelWidget->move(100, 100);
|
||||
m_modelWidget->setGraphicsView(this);
|
||||
m_modelWidgetProxy = scene()->addWidget(m_modelWidget);
|
||||
|
||||
m_backgroundItem = new QGraphicsPixmapItem();
|
||||
m_backgroundItem->setOpacity(0.25);
|
||||
scene()->addItem(m_backgroundItem);
|
||||
|
||||
m_pendingNodeItem = new QGraphicsEllipseItem(0, 0, m_initialNodeSize, m_initialNodeSize);
|
||||
m_pendingNodeItem->setVisible(false);
|
||||
scene()->addItem(m_pendingNodeItem);
|
||||
|
||||
m_pendingEdgeItem = new QGraphicsLineItem(0, 0, 0, 0);
|
||||
m_pendingEdgeItem->setVisible(false);
|
||||
scene()->addItem(m_pendingEdgeItem);
|
||||
}
|
||||
|
||||
ModelWidget *SkeletonEditGraphicsView::modelWidget()
|
||||
{
|
||||
return m_modelWidget;
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::toggleAddNodeMode()
|
||||
{
|
||||
if (!m_backgroundLoaded)
|
||||
return;
|
||||
m_inAddNodeMode = !m_inAddNodeMode;
|
||||
applyAddNodeMode();
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::applyAddNodeMode()
|
||||
{
|
||||
m_pendingNodeItem->setVisible(m_inAddNodeMode);
|
||||
m_pendingEdgeItem->setVisible(m_inAddNodeMode && m_nextStartNodeItem);
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::turnOffAddNodeMode()
|
||||
{
|
||||
if (!m_backgroundLoaded)
|
||||
return;
|
||||
m_inAddNodeMode = false;
|
||||
applyAddNodeMode();
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::turnOnAddNodeMode()
|
||||
{
|
||||
if (!m_backgroundLoaded)
|
||||
return;
|
||||
m_inAddNodeMode = true;
|
||||
applyAddNodeMode();
|
||||
}
|
||||
|
||||
SkeletonEditNodeItem *SkeletonEditGraphicsView::findNodeItemByPos(QPointF pos)
|
||||
{
|
||||
QList<QGraphicsItem *>::iterator it;
|
||||
QList<QGraphicsItem *> list = scene()->items();
|
||||
for (it = list.begin(); it != list.end(); ++it) {
|
||||
if ((*it)->data(0).toString() == "node") {
|
||||
SkeletonEditNodeItem *nodeItem = static_cast<SkeletonEditNodeItem *>(*it);
|
||||
if (nodeItem->shape().contains(pos)) {
|
||||
return nodeItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SkeletonEditEdgeItem *SkeletonEditGraphicsView::findEdgeItemByPos(QPointF pos)
|
||||
{
|
||||
QList<QGraphicsItem *>::iterator it;
|
||||
QList<QGraphicsItem *> list = scene()->items();
|
||||
for (it = list.begin(); it != list.end(); ++it) {
|
||||
if ((*it)->data(0).toString() == "edge") {
|
||||
SkeletonEditEdgeItem *edgeItem = static_cast<SkeletonEditEdgeItem *>(*it);
|
||||
if (edgeItem->shape().contains(pos)) {
|
||||
return edgeItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SkeletonEditEdgeItem *SkeletonEditGraphicsView::findEdgeItemByNodePair(SkeletonEditNodeItem *first,
|
||||
SkeletonEditNodeItem *second)
|
||||
{
|
||||
QList<QGraphicsItem *>::iterator it;
|
||||
QList<QGraphicsItem *> list = scene()->items();
|
||||
assert(first != second);
|
||||
for (it = list.begin(); it != list.end(); ++it) {
|
||||
if ((*it)->data(0).toString() == "edge") {
|
||||
SkeletonEditEdgeItem *edgeItem = static_cast<SkeletonEditEdgeItem *>(*it);
|
||||
if ((edgeItem->firstNode() == first || edgeItem->secondNode() == first) &&
|
||||
(edgeItem->firstNode() == second || edgeItem->secondNode() == second)) {
|
||||
return edgeItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool SkeletonEditGraphicsView::mousePress(QMouseEvent *event, const QPointF &scenePos)
|
||||
{
|
||||
bool processed = false;
|
||||
if (!m_backgroundLoaded)
|
||||
return false;
|
||||
QPointF pos = scenePos;
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (!m_inAddNodeMode) {
|
||||
if (m_lastHoverNodeItem) {
|
||||
setNextStartNodeItem(m_lastHoverNodeItem);
|
||||
m_lastHoverNodeItem = NULL;
|
||||
processed = true;
|
||||
} else {
|
||||
if (m_nextStartNodeItem) {
|
||||
setNextStartNodeItem(NULL);
|
||||
processed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_lastMousePos = pos;
|
||||
return processed;
|
||||
}
|
||||
|
||||
bool SkeletonEditGraphicsView::mouseDoubleClick(QMouseEvent *event, const QPointF &scenePos)
|
||||
{
|
||||
bool processed = false;
|
||||
if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
|
||||
processed = true;
|
||||
emit changeTurnaroundTriggered();
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::removeSelectedItems()
|
||||
{
|
||||
if (m_nextStartNodeItem) {
|
||||
SkeletonEditNodeItem *nodeItem = m_nextStartNodeItem;
|
||||
setNextStartNodeItem(NULL);
|
||||
removeNodeItem(nodeItem->nextSidePair());
|
||||
removeNodeItem(nodeItem);
|
||||
emit nodesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::removeNodeItem(SkeletonEditNodeItem *nodeItem)
|
||||
{
|
||||
scene()->removeItem(nodeItem);
|
||||
QList<QGraphicsItem *>::iterator it;
|
||||
QList<QGraphicsItem *> list = scene()->items();
|
||||
for (it = list.begin(); it != list.end(); ++it) {
|
||||
if ((*it)->data(0).toString() == "edge") {
|
||||
SkeletonEditEdgeItem *edgeItem = static_cast<SkeletonEditEdgeItem *>(*it);
|
||||
if (edgeItem->firstNode() == nodeItem || edgeItem->secondNode() == nodeItem) {
|
||||
scene()->removeItem(edgeItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::fetchNodeItemAndAllSidePairs(SkeletonEditNodeItem *nodeItem, std::vector<SkeletonEditNodeItem *> *sidePairs)
|
||||
{
|
||||
sidePairs->push_back(nodeItem);
|
||||
sidePairs->push_back(nodeItem->nextSidePair());
|
||||
sidePairs->push_back(nodeItem->nextSidePair()->nextSidePair());
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::removeNodeItemAndSidePairs(SkeletonEditNodeItem *nodeItem)
|
||||
{
|
||||
std::vector<SkeletonEditNodeItem *> nodes;
|
||||
fetchNodeItemAndAllSidePairs(nodeItem, &nodes);
|
||||
for (size_t i = 0; i < nodes.size(); i++) {
|
||||
removeNodeItem(nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool SkeletonEditGraphicsView::keyPress(QKeyEvent *event, const QPointF &scenePos)
|
||||
{
|
||||
bool processed = false;
|
||||
if (!m_backgroundLoaded)
|
||||
return false;
|
||||
if (event->key() == Qt::Key_A) {
|
||||
toggleAddNodeMode();
|
||||
processed = true;
|
||||
} else if (event->key() == Qt::Key_C) {
|
||||
m_combineEnabled = !m_combineEnabled;
|
||||
emit nodesChanged();
|
||||
} else if (event->key() == Qt::Key_U) {
|
||||
m_unionEnabled = !m_unionEnabled;
|
||||
emit nodesChanged();
|
||||
} else if (event->key() == Qt::Key_S) {
|
||||
m_subdivEnabled = !m_subdivEnabled;
|
||||
emit nodesChanged();
|
||||
} else if (event->key() == Qt::Key_Tab) {
|
||||
if (m_nextStartNodeItem)
|
||||
setNextStartNodeItem(m_nextStartNodeItem->nextSidePair());
|
||||
} else if (event->key() == Qt::Key_Delete || event->key() ==Qt::Key_Backspace) {
|
||||
removeSelectedItems();
|
||||
processed = true;
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QFrame::resizeEvent(event);
|
||||
emit sizeChanged();
|
||||
}
|
||||
|
||||
SkeletonEditNodeItem *SkeletonEditGraphicsView::addNodeItemAndSidePairs(QRectF area, SkeletonEditNodeItem *fromNodeItem, const QString &sideColorName)
|
||||
{
|
||||
float pairedX = 0;
|
||||
QRectF pairedRect = area;
|
||||
if (fromNodeItem) {
|
||||
pairedX = fromNodeItem->nextSidePair()->rect().x();
|
||||
} else {
|
||||
if (area.center().x() < scene()->sceneRect().width() / 2) {
|
||||
pairedX = area.center().x() + scene()->sceneRect().width() / 4;
|
||||
} else {
|
||||
pairedX = area.center().x() - scene()->sceneRect().width() / 4;
|
||||
}
|
||||
}
|
||||
pairedRect.translate(pairedX - area.x(), 0);
|
||||
SkeletonEditNodeItem *firstNode = new SkeletonEditNodeItem(area);
|
||||
scene()->addItem(firstNode);
|
||||
firstNode->setSideColorName(fromNodeItem ? fromNodeItem->sideColorName() : sideColorName);
|
||||
SkeletonEditNodeItem *secondNode = new SkeletonEditNodeItem(pairedRect);
|
||||
scene()->addItem(secondNode);
|
||||
secondNode->setSideColorName(firstNode->nextSideColorName());
|
||||
firstNode->setNextSidePair(secondNode);
|
||||
secondNode->setNextSidePair(firstNode);
|
||||
setNextStartNodeItem(firstNode);
|
||||
if (!fromNodeItem) {
|
||||
return firstNode;
|
||||
}
|
||||
addEdgeItem(fromNodeItem, firstNode);
|
||||
addEdgeItem(fromNodeItem->nextSidePair(), firstNode->nextSidePair());
|
||||
return firstNode;
|
||||
}
|
||||
|
||||
SkeletonEditNodeItem *SkeletonEditGraphicsView::addNodeItem(float originX, float originY, float radius)
|
||||
{
|
||||
QRectF area(originX - radius, originY - radius, radius * 2, radius * 2);
|
||||
SkeletonEditNodeItem *firstNode = new SkeletonEditNodeItem(area);
|
||||
scene()->addItem(firstNode);
|
||||
return firstNode;
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::addEdgeItem(SkeletonEditNodeItem *first, SkeletonEditNodeItem *second)
|
||||
{
|
||||
SkeletonEditEdgeItem *newEdge = new SkeletonEditEdgeItem();
|
||||
newEdge->setNodes(first, second);
|
||||
scene()->addItem(newEdge);
|
||||
}
|
||||
|
||||
bool SkeletonEditGraphicsView::mouseRelease(QMouseEvent *event, const QPointF &scenePos)
|
||||
{
|
||||
bool processed = false;
|
||||
if (!m_backgroundLoaded)
|
||||
return false;
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (m_inAddNodeMode) {
|
||||
if (m_lastHoverNodeItem && m_nextStartNodeItem &&
|
||||
m_lastHoverNodeItem != m_nextStartNodeItem &&
|
||||
m_lastHoverNodeItem->sideColor() == m_nextStartNodeItem->sideColor()) {
|
||||
if (!findEdgeItemByNodePair(m_lastHoverNodeItem, m_nextStartNodeItem)) {
|
||||
addEdgeItem(m_nextStartNodeItem, m_lastHoverNodeItem);
|
||||
addEdgeItem(m_nextStartNodeItem->nextSidePair(), m_lastHoverNodeItem->nextSidePair());
|
||||
processed = true;
|
||||
emit nodesChanged();
|
||||
}
|
||||
} else {
|
||||
addNodeItemAndSidePairs(m_pendingNodeItem->rect(), m_nextStartNodeItem);
|
||||
processed = true;
|
||||
emit nodesChanged();
|
||||
}
|
||||
}
|
||||
m_isMovingNodeItem = false;
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
bool SkeletonEditGraphicsView::canNodeItemMoveTo(SkeletonEditNodeItem *item, QPointF moveTo)
|
||||
{
|
||||
if (moveTo.x() < 0)
|
||||
return false;
|
||||
if (moveTo.y() < 0)
|
||||
return false;
|
||||
if (moveTo.x() + item->rect().width() >= scene()->sceneRect().width())
|
||||
return false;
|
||||
if (moveTo.y() + item->rect().height() >= scene()->sceneRect().height())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkeletonEditGraphicsView::mouseMove(QMouseEvent *event, const QPointF &scenePos)
|
||||
{
|
||||
bool processed = false;
|
||||
if (!m_backgroundLoaded)
|
||||
return false;
|
||||
QPointF pos = scenePos;
|
||||
QPointF moveTo = QPointF(pos.x() - m_pendingNodeItem->rect().width() / 2, pos.y() - m_pendingNodeItem->rect().height() / 2);
|
||||
if (moveTo.x() < 0)
|
||||
moveTo.setX(0);
|
||||
if (moveTo.y() < 0)
|
||||
moveTo.setY(0);
|
||||
if (moveTo.x() + m_pendingNodeItem->rect().width() >= scene()->sceneRect().width())
|
||||
moveTo.setX(scene()->sceneRect().width() - m_pendingNodeItem->rect().width());
|
||||
if (moveTo.y() + m_pendingNodeItem->rect().height() >= scene()->sceneRect().height())
|
||||
moveTo.setY(scene()->sceneRect().height() - m_pendingNodeItem->rect().height());
|
||||
QSizeF oldSize = m_pendingNodeItem->rect().size();
|
||||
m_pendingNodeItem->setRect(moveTo.x(), moveTo.y(),
|
||||
oldSize.width(),
|
||||
oldSize.height());
|
||||
if (m_nextStartNodeItem) {
|
||||
m_pendingEdgeItem->setLine(QLineF(m_nextStartNodeItem->origin(), QPointF(moveTo.x() + m_pendingNodeItem->rect().width() / 2,
|
||||
moveTo.y() + m_pendingNodeItem->rect().height() / 2)));
|
||||
}
|
||||
if (!m_isMovingNodeItem) {
|
||||
SkeletonEditNodeItem *hoverNodeItem = findNodeItemByPos(pos);
|
||||
if (hoverNodeItem) {
|
||||
hoverNodeItem->setHovered(true);
|
||||
processed = true;
|
||||
}
|
||||
if (hoverNodeItem != m_lastHoverNodeItem) {
|
||||
if (m_lastHoverNodeItem)
|
||||
m_lastHoverNodeItem->setHovered(false);
|
||||
m_lastHoverNodeItem = hoverNodeItem;
|
||||
processed = true;
|
||||
}
|
||||
}
|
||||
QPointF curMousePos = pos;
|
||||
if (m_lastHoverNodeItem) {
|
||||
if ((event->buttons() & Qt::LeftButton) &&
|
||||
(curMousePos != m_lastMousePos || m_isMovingNodeItem)) {
|
||||
m_isMovingNodeItem = true;
|
||||
QRectF rect = m_lastHoverNodeItem->rect();
|
||||
QRectF pairedRect;
|
||||
|
||||
rect.translate(curMousePos.x() - m_lastMousePos.x(), curMousePos.y() - m_lastMousePos.y());
|
||||
pairedRect = m_lastHoverNodeItem->nextSidePair()->rect();
|
||||
pairedRect.translate(0, curMousePos.y() - m_lastMousePos.y());
|
||||
|
||||
m_lastHoverNodeItem->setRect(rect);
|
||||
m_lastHoverNodeItem->nextSidePair()->setRect(pairedRect);
|
||||
|
||||
QList<QGraphicsItem *>::iterator it;
|
||||
QList<QGraphicsItem *> list = scene()->items();
|
||||
for (it = list.begin(); it != list.end(); ++it) {
|
||||
if ((*it)->data(0).toString() == "edge") {
|
||||
SkeletonEditEdgeItem *edgeItem = static_cast<SkeletonEditEdgeItem *>(*it);
|
||||
if (edgeItem->connects(m_lastHoverNodeItem) ||
|
||||
edgeItem->connects(m_lastHoverNodeItem->nextSidePair()))
|
||||
edgeItem->updatePosition();
|
||||
}
|
||||
}
|
||||
processed = true;
|
||||
emit nodesChanged();
|
||||
}
|
||||
}
|
||||
m_lastMousePos = curMousePos;
|
||||
return processed;
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::AddItemRadius(QGraphicsEllipseItem *item, float delta)
|
||||
{
|
||||
QSizeF oldSize = item->rect().size();
|
||||
QPointF originPt = QPointF(item->rect().left() + oldSize.width() / 2,
|
||||
item->rect().top() + oldSize.height() / 2);
|
||||
QSizeF newSize = QSizeF(oldSize.width() + delta, oldSize.height() + delta);
|
||||
if (newSize.width() < m_minimalNodeSize || newSize.height() < m_minimalNodeSize) {
|
||||
newSize.setWidth(m_minimalNodeSize);
|
||||
newSize.setHeight(m_minimalNodeSize);
|
||||
}
|
||||
QPointF newLeftTop = QPointF(originPt.x() - newSize.width() / 2,
|
||||
originPt.y() - newSize.height() / 2);
|
||||
if (newLeftTop.x() < 0 || newLeftTop.x() + newSize.width() >= scene()->sceneRect().width())
|
||||
return;
|
||||
if (newLeftTop.y() < 0 || newLeftTop.y() + newSize.height() >= scene()->sceneRect().height())
|
||||
return;
|
||||
item->setRect(newLeftTop.x(),
|
||||
newLeftTop.y(),
|
||||
newSize.width(),
|
||||
newSize.height());
|
||||
}
|
||||
|
||||
bool SkeletonEditGraphicsView::canAddItemRadius(QGraphicsEllipseItem *item, float delta)
|
||||
{
|
||||
QSizeF oldSize = item->rect().size();
|
||||
QPointF originPt = QPointF(item->rect().left() + oldSize.width() / 2,
|
||||
item->rect().top() + oldSize.height() / 2);
|
||||
QSizeF newSize = QSizeF(oldSize.width() + delta, oldSize.height() + delta);
|
||||
if (newSize.width() < m_minimalNodeSize || newSize.height() < m_minimalNodeSize) {
|
||||
newSize.setWidth(m_minimalNodeSize);
|
||||
newSize.setHeight(m_minimalNodeSize);
|
||||
}
|
||||
QPointF newLeftTop = QPointF(originPt.x() - newSize.width() / 2,
|
||||
originPt.y() - newSize.height() / 2);
|
||||
if (newLeftTop.x() < 0 || newLeftTop.x() + newSize.width() >= scene()->sceneRect().width())
|
||||
return false;
|
||||
if (newLeftTop.y() < 0 || newLeftTop.y() + newSize.height() >= scene()->sceneRect().height())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkeletonEditGraphicsView::wheel(QWheelEvent *event, const QPointF &scenePos)
|
||||
{
|
||||
bool processed = false;
|
||||
if (!m_backgroundLoaded)
|
||||
return false;
|
||||
qreal delta = event->delta() / 10;
|
||||
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) {
|
||||
if (delta > 0)
|
||||
delta = 1;
|
||||
else
|
||||
delta = -1;
|
||||
} else {
|
||||
if (fabs(delta) < 1)
|
||||
delta = delta < 0 ? -1.0 : 1.0;
|
||||
}
|
||||
AddItemRadius(m_pendingNodeItem, delta);
|
||||
if (!m_inAddNodeMode && m_lastHoverNodeItem) {
|
||||
AddItemRadius(m_lastHoverNodeItem, delta);
|
||||
AddItemRadius(m_lastHoverNodeItem->nextSidePair(), delta);
|
||||
processed = true;
|
||||
emit nodesChanged();
|
||||
}
|
||||
if (m_inAddNodeMode)
|
||||
processed = true;
|
||||
return processed;
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::setNextStartNodeItem(SkeletonEditNodeItem *item)
|
||||
{
|
||||
if (m_nextStartNodeItem != item) {
|
||||
if (m_nextStartNodeItem)
|
||||
m_nextStartNodeItem->setChecked(false);
|
||||
}
|
||||
m_nextStartNodeItem = item;
|
||||
if (m_nextStartNodeItem)
|
||||
m_nextStartNodeItem->setChecked(true);
|
||||
applyAddNodeMode();
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::updateBackgroundImage(const QImage &image)
|
||||
{
|
||||
QSizeF oldSceneSize = scene()->sceneRect().size();
|
||||
QPixmap pixmap = QPixmap::fromImage(image);
|
||||
scene()->setSceneRect(pixmap.rect());
|
||||
m_backgroundItem->setPixmap(pixmap);
|
||||
adjustItems(oldSceneSize, scene()->sceneRect().size());
|
||||
if (!m_backgroundLoaded) {
|
||||
m_backgroundLoaded = true;
|
||||
applyAddNodeMode();
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap SkeletonEditGraphicsView::backgroundImage()
|
||||
{
|
||||
return m_backgroundItem->pixmap();
|
||||
}
|
||||
|
||||
bool SkeletonEditGraphicsView::hasBackgroundImage()
|
||||
{
|
||||
return m_backgroundLoaded;
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::adjustItems(QSizeF oldSceneSize, QSizeF newSceneSize)
|
||||
{
|
||||
if (oldSceneSize == newSceneSize)
|
||||
return;
|
||||
float radiusMul = (float)newSceneSize.height() / oldSceneSize.height();
|
||||
float xMul = (float)newSceneSize.width() / oldSceneSize.width();
|
||||
float yMul = radiusMul;
|
||||
QList<QGraphicsItem *>::iterator it;
|
||||
QList<QGraphicsItem *> list = scene()->items();
|
||||
for (it = list.begin(); it != list.end(); ++it) {
|
||||
if ((*it)->data(0).toString() == "node") {
|
||||
SkeletonEditNodeItem *nodeItem = static_cast<SkeletonEditNodeItem *>(*it);
|
||||
nodeItem->setRadius(nodeItem->radius() * radiusMul);
|
||||
QPointF oldOrigin = nodeItem->origin();
|
||||
nodeItem->setOrigin(QPointF(oldOrigin.x() * xMul, oldOrigin.y() * yMul));
|
||||
}
|
||||
}
|
||||
for (it = list.begin(); it != list.end(); ++it) {
|
||||
if ((*it)->data(0).toString() == "edge") {
|
||||
SkeletonEditEdgeItem *edgeItem = static_cast<SkeletonEditEdgeItem *>(*it);
|
||||
edgeItem->updatePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::loadFromSnapshot(SkeletonSnapshot *snapshot)
|
||||
{
|
||||
float radiusMul = 1.0;
|
||||
float xMul = 1.0;
|
||||
float yMul = radiusMul;
|
||||
|
||||
QString canvasWidth = snapshot->canvas["width"];
|
||||
QString canvasHeight = snapshot->canvas["height"];
|
||||
float canvasWidthVal = canvasWidth.toFloat();
|
||||
float canvasHeightVal = canvasHeight.toFloat();
|
||||
if (!hasBackgroundImage()) {
|
||||
QPixmap emptyImage((int)canvasWidthVal, (int)canvasHeightVal);
|
||||
emptyImage.fill(QWidget::palette().color(QWidget::backgroundRole()));
|
||||
updateBackgroundImage(emptyImage.toImage());
|
||||
}
|
||||
if (canvasHeightVal > 0)
|
||||
radiusMul = (float)scene()->sceneRect().height() / canvasHeightVal;
|
||||
if (canvasWidthVal > 0)
|
||||
xMul = (float)scene()->sceneRect().width() / canvasWidthVal;
|
||||
yMul = radiusMul;
|
||||
|
||||
std::map<QString, SkeletonEditNodeItem *> nodeItemMap;
|
||||
std::map<QString, std::map<QString, QString>>::iterator nodeIterator;
|
||||
for (nodeIterator = snapshot->nodes.begin(); nodeIterator != snapshot->nodes.end(); nodeIterator++) {
|
||||
std::map<QString, QString> *snapshotNode = &nodeIterator->second;
|
||||
SkeletonEditNodeItem *nodeItem = addNodeItem((*snapshotNode)["x"].toFloat() * xMul,
|
||||
(*snapshotNode)["y"].toFloat() * yMul,
|
||||
(*snapshotNode)["radius"].toFloat() * radiusMul);
|
||||
nodeItem->setSideColorName((*snapshotNode)["sideColorName"]);
|
||||
nodeItem->markAsBranch("true" == (*snapshotNode)["isBranch"]);
|
||||
nodeItemMap[nodeIterator->first] = nodeItem;
|
||||
}
|
||||
for (nodeIterator = snapshot->nodes.begin(); nodeIterator != snapshot->nodes.end(); nodeIterator++) {
|
||||
std::map<QString, QString> *snapshotNode = &nodeIterator->second;
|
||||
SkeletonEditNodeItem *nodeItem = nodeItemMap[nodeIterator->first];
|
||||
nodeItem->setNextSidePair(nodeItemMap[(*snapshotNode)["nextSidePair"]]);
|
||||
}
|
||||
|
||||
std::map<QString, std::map<QString, QString>>::iterator edgeIterator;
|
||||
for (edgeIterator = snapshot->edges.begin(); edgeIterator != snapshot->edges.end(); edgeIterator++) {
|
||||
std::map<QString, QString> *snapshotEdge = &edgeIterator->second;
|
||||
addEdgeItem(nodeItemMap[(*snapshotEdge)["from"]], nodeItemMap[(*snapshotEdge)["to"]]);
|
||||
}
|
||||
|
||||
emit nodesChanged();
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::saveToSnapshot(SkeletonSnapshot *snapshot)
|
||||
{
|
||||
snapshot->canvas["width"] = QString("%1").arg(scene()->sceneRect().width());
|
||||
snapshot->canvas["height"] = QString("%1").arg(scene()->sceneRect().height());
|
||||
snapshot->canvas["combine"] = m_combineEnabled ? "true" : "false";
|
||||
snapshot->canvas["union"] = m_unionEnabled ? "true" : "false";
|
||||
snapshot->canvas["subdiv"] = m_subdivEnabled ? "true" : "false";
|
||||
|
||||
QList<QGraphicsItem *>::iterator it;
|
||||
QList<QGraphicsItem *> list = scene()->items();
|
||||
|
||||
int nextNodeId = 1;
|
||||
std::map<SkeletonEditNodeItem *, QString> nodeIdMap;
|
||||
for (it = list.begin(); it != list.end(); ++it) {
|
||||
if ((*it)->data(0).toString() == "node") {
|
||||
SkeletonEditNodeItem *nodeItem = static_cast<SkeletonEditNodeItem *>(*it);
|
||||
QString nodeId = QString("node%1").arg(nextNodeId);
|
||||
std::map<QString, QString> *snapshotNode = &snapshot->nodes[nodeId];
|
||||
(*snapshotNode)["id"] = nodeId;
|
||||
(*snapshotNode)["sideColorName"] = nodeItem->sideColorName();
|
||||
(*snapshotNode)["radius"] = QString("%1").arg(nodeItem->radius());
|
||||
(*snapshotNode)["isBranch"] = QString("%1").arg(nodeItem->isBranch() ? "true" : "false");
|
||||
(*snapshotNode)["isRoot"] = QString("%1").arg(nodeItem->isRoot() ? "true" : "false");
|
||||
QPointF origin = nodeItem->origin();
|
||||
(*snapshotNode)["x"] = QString("%1").arg(origin.x());
|
||||
(*snapshotNode)["y"] = QString("%1").arg(origin.y());
|
||||
nodeIdMap[nodeItem] = nodeId;
|
||||
nextNodeId++;
|
||||
}
|
||||
}
|
||||
|
||||
for (it = list.begin(); it != list.end(); ++it) {
|
||||
if ((*it)->data(0).toString() == "node") {
|
||||
SkeletonEditNodeItem *nodeItem = static_cast<SkeletonEditNodeItem *>(*it);
|
||||
QString nodeId = nodeIdMap[nodeItem];
|
||||
std::map<QString, QString> *snapshotNode = &snapshot->nodes[nodeId];
|
||||
(*snapshotNode)["nextSidePair"] = nodeIdMap[nodeItem->nextSidePair()];
|
||||
}
|
||||
}
|
||||
|
||||
int nextEdgeId = 1;
|
||||
for (it = list.begin(); it != list.end(); ++it) {
|
||||
if ((*it)->data(0).toString() == "edge") {
|
||||
SkeletonEditEdgeItem *edgeItem = static_cast<SkeletonEditEdgeItem *>(*it);
|
||||
QString edgeId = QString("edge%1").arg(nextEdgeId);
|
||||
std::map<QString, QString> *snapshotEdge = &snapshot->edges[edgeId];
|
||||
(*snapshotEdge)["id"] = edgeId;
|
||||
(*snapshotEdge)["from"] = nodeIdMap[edgeItem->firstNode()];
|
||||
(*snapshotEdge)["to"] = nodeIdMap[edgeItem->secondNode()];
|
||||
nextEdgeId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::markAsBranch()
|
||||
{
|
||||
if (m_nextStartNodeItem) {
|
||||
m_nextStartNodeItem->markAsBranch(true);
|
||||
m_nextStartNodeItem->nextSidePair()->markAsBranch(true);
|
||||
emit nodesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::markAsTrunk()
|
||||
{
|
||||
if (m_nextStartNodeItem) {
|
||||
m_nextStartNodeItem->markAsBranch(false);
|
||||
m_nextStartNodeItem->nextSidePair()->markAsBranch(false);
|
||||
emit nodesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::markAsRoot()
|
||||
{
|
||||
if (m_nextStartNodeItem) {
|
||||
m_nextStartNodeItem->markAsRoot(true);
|
||||
m_nextStartNodeItem->nextSidePair()->markAsRoot(true);
|
||||
emit nodesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::markAsChild()
|
||||
{
|
||||
if (m_nextStartNodeItem) {
|
||||
m_nextStartNodeItem->markAsRoot(false);
|
||||
m_nextStartNodeItem->nextSidePair()->markAsRoot(false);
|
||||
emit nodesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
mouseMove(event, mapToScene(event->pos()));
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
wheel(event, mapToScene(event->pos()));
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
mouseRelease(event, mapToScene(event->pos()));
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
mousePress(event, mapToScene(event->pos()));
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
mouseDoubleClick(event, mapToScene(event->pos()));
|
||||
}
|
||||
|
||||
void SkeletonEditGraphicsView::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
keyPress(event, QPointF());
|
||||
}
|
||||
|
||||
bool SkeletonEditGraphicsView::combineEnabled()
|
||||
{
|
||||
return m_combineEnabled;
|
||||
}
|
||||
|
||||
bool SkeletonEditGraphicsView::unionEnabled()
|
||||
{
|
||||
return m_unionEnabled;
|
||||
}
|
||||
|
||||
bool SkeletonEditGraphicsView::subdivEnabled()
|
||||
{
|
||||
return m_subdivEnabled;
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
#ifndef SKELETON_EDIT_GRAPHICS_VIEW_H
|
||||
#define SKELETON_EDIT_GRAPHICS_VIEW_H
|
||||
#include <QGraphicsView>
|
||||
#include <QMouseEvent>
|
||||
#include <QWheelEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QXmlStreamWriter>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QGraphicsProxyWidget>
|
||||
#include "skeletoneditnodeitem.h"
|
||||
#include "skeletoneditedgeitem.h"
|
||||
#include "skeletonsnapshot.h"
|
||||
#include "modelwidget.h"
|
||||
|
||||
class SkeletonEditGraphicsView : public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void sizeChanged();
|
||||
void nodesChanged();
|
||||
void changeTurnaroundTriggered();
|
||||
void edgeCheckStateChanged();
|
||||
public slots:
|
||||
void turnOffAddNodeMode();
|
||||
void turnOnAddNodeMode();
|
||||
void markAsBranch();
|
||||
void markAsTrunk();
|
||||
void markAsRoot();
|
||||
void markAsChild();
|
||||
bool mouseMove(QMouseEvent *event, const QPointF &scenePos);
|
||||
bool wheel(QWheelEvent *event, const QPointF &scenePos);
|
||||
bool mouseRelease(QMouseEvent *event, const QPointF &scenePos);
|
||||
bool mousePress(QMouseEvent *event, const QPointF &scenePos);
|
||||
bool mouseDoubleClick(QMouseEvent *event, const QPointF &scenePos);
|
||||
bool keyPress(QKeyEvent *event, const QPointF &scenePos);
|
||||
public:
|
||||
SkeletonEditGraphicsView(QWidget *parent = 0);
|
||||
void updateBackgroundImage(const QImage &image);
|
||||
bool hasBackgroundImage();
|
||||
QPixmap backgroundImage();
|
||||
void saveToSnapshot(SkeletonSnapshot *snapshot);
|
||||
void loadFromSnapshot(SkeletonSnapshot *snapshot);
|
||||
ModelWidget *modelWidget();
|
||||
bool combineEnabled();
|
||||
bool unionEnabled();
|
||||
bool subdivEnabled();
|
||||
protected:
|
||||
void mouseMoveEvent(QMouseEvent *event);
|
||||
void wheelEvent(QWheelEvent *event);
|
||||
void mouseReleaseEvent(QMouseEvent *event);
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
void mouseDoubleClickEvent(QMouseEvent *event);
|
||||
void keyPressEvent(QKeyEvent *event);
|
||||
void resizeEvent(QResizeEvent *event);
|
||||
private:
|
||||
QGraphicsPixmapItem *m_backgroundItem;
|
||||
QGraphicsEllipseItem *m_pendingNodeItem;
|
||||
QGraphicsLineItem *m_pendingEdgeItem;
|
||||
static qreal m_initialNodeSize;
|
||||
static qreal m_minimalNodeSize;
|
||||
bool m_inAddNodeMode;
|
||||
SkeletonEditNodeItem *m_nextStartNodeItem;
|
||||
SkeletonEditNodeItem *m_lastHoverNodeItem;
|
||||
QPointF m_lastMousePos;
|
||||
bool m_isMovingNodeItem;
|
||||
bool m_backgroundLoaded;
|
||||
ModelWidget *m_modelWidget;
|
||||
QGraphicsProxyWidget *m_modelWidgetProxy;
|
||||
bool m_combineEnabled;
|
||||
bool m_unionEnabled;
|
||||
bool m_subdivEnabled;
|
||||
private:
|
||||
void toggleAddNodeMode();
|
||||
void applyAddNodeMode();
|
||||
SkeletonEditNodeItem *findNodeItemByPos(QPointF pos);
|
||||
SkeletonEditEdgeItem *findEdgeItemByPos(QPointF pos);
|
||||
SkeletonEditEdgeItem *findEdgeItemByNodePair(SkeletonEditNodeItem *first,
|
||||
SkeletonEditNodeItem *second);
|
||||
void setNextStartNodeItem(SkeletonEditNodeItem *item);
|
||||
bool canNodeItemMoveTo(SkeletonEditNodeItem *item, QPointF moveTo);
|
||||
void AddItemRadius(QGraphicsEllipseItem *item, float delta);
|
||||
bool canAddItemRadius(QGraphicsEllipseItem *item, float delta);
|
||||
void adjustItems(QSizeF oldSceneSize, QSizeF newSceneSize);
|
||||
void removeSelectedItems();
|
||||
void removeNodeItem(SkeletonEditNodeItem *nodeItem);
|
||||
void removeNodeItemAndSidePairs(SkeletonEditNodeItem *nodeItem);
|
||||
void fetchNodeItemAndAllSidePairs(SkeletonEditNodeItem *nodeItem, std::vector<SkeletonEditNodeItem *> *sidePairs);
|
||||
SkeletonEditNodeItem *addNodeItemAndSidePairs(QRectF area, SkeletonEditNodeItem *fromNodeItem, const QString &sideColorName="red");
|
||||
SkeletonEditNodeItem *addNodeItem(float originX, float originY, float radius);
|
||||
void addEdgeItem(SkeletonEditNodeItem *first, SkeletonEditNodeItem *second);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,140 +0,0 @@
|
|||
#include <QPen>
|
||||
#include "skeletoneditnodeitem.h"
|
||||
#include "theme.h"
|
||||
|
||||
SkeletonEditNodeItem::SkeletonEditNodeItem(const QRectF &rect, QGraphicsItem *parent) :
|
||||
QGraphicsEllipseItem(rect, parent),
|
||||
m_hovered(false),
|
||||
m_checked(false),
|
||||
m_nextSidePair(NULL),
|
||||
m_sideColor(Theme::red),
|
||||
m_sideColorName("red"),
|
||||
m_isBranch(false),
|
||||
m_isRoot(false)
|
||||
{
|
||||
setData(0, "node");
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
void SkeletonEditNodeItem::markAsBranch(bool isBranch)
|
||||
{
|
||||
m_isBranch = isBranch;
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
bool SkeletonEditNodeItem::isBranch()
|
||||
{
|
||||
return m_isBranch;
|
||||
}
|
||||
|
||||
void SkeletonEditNodeItem::markAsRoot(bool isRoot)
|
||||
{
|
||||
m_isRoot = isRoot;
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
bool SkeletonEditNodeItem::isRoot()
|
||||
{
|
||||
return m_isRoot;
|
||||
}
|
||||
|
||||
const QString &SkeletonEditNodeItem::sideColorName()
|
||||
{
|
||||
return m_sideColorName;
|
||||
}
|
||||
|
||||
QString SkeletonEditNodeItem::nextSideColorName()
|
||||
{
|
||||
return Theme::nextSideColorNameMap[m_sideColorName];
|
||||
}
|
||||
|
||||
void SkeletonEditNodeItem::setSideColorName(const QString &name)
|
||||
{
|
||||
m_sideColorName = name;
|
||||
m_sideColor = Theme::sideColorNameToColorMap[m_sideColorName];
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
bool SkeletonEditNodeItem::hovered()
|
||||
{
|
||||
return m_hovered;
|
||||
}
|
||||
|
||||
void SkeletonEditNodeItem::setHovered(bool hovered)
|
||||
{
|
||||
m_hovered = hovered;
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
bool SkeletonEditNodeItem::checked()
|
||||
{
|
||||
return m_checked;
|
||||
}
|
||||
|
||||
void SkeletonEditNodeItem::setChecked(bool checked)
|
||||
{
|
||||
m_checked = checked;
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
SkeletonEditNodeItem *SkeletonEditNodeItem::nextSidePair()
|
||||
{
|
||||
return m_nextSidePair;
|
||||
}
|
||||
|
||||
void SkeletonEditNodeItem::setNextSidePair(SkeletonEditNodeItem *nodeItem)
|
||||
{
|
||||
m_nextSidePair = nodeItem;
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
const QColor &SkeletonEditNodeItem::sideColor()
|
||||
{
|
||||
return m_sideColor;
|
||||
}
|
||||
|
||||
QPointF SkeletonEditNodeItem::origin()
|
||||
{
|
||||
return QPointF(rect().x() + rect().width() / 2,
|
||||
rect().y() + rect().height() / 2);
|
||||
}
|
||||
|
||||
float SkeletonEditNodeItem::radius()
|
||||
{
|
||||
return rect().width() / 2;
|
||||
}
|
||||
|
||||
void SkeletonEditNodeItem::setRadius(float radius)
|
||||
{
|
||||
QPointF oldOrigin = origin();
|
||||
setRect(oldOrigin.x() - radius, oldOrigin.y() - radius,
|
||||
radius * 2, radius * 2);
|
||||
}
|
||||
|
||||
void SkeletonEditNodeItem::setOrigin(QPointF point)
|
||||
{
|
||||
QPointF moveBy = point - origin();
|
||||
QRectF newRect = rect();
|
||||
newRect.adjust(moveBy.x(), moveBy.y(), moveBy.x(), moveBy.y());
|
||||
setRect(newRect);
|
||||
}
|
||||
|
||||
void SkeletonEditNodeItem::updateAppearance()
|
||||
{
|
||||
QColor penColor = m_sideColor;
|
||||
penColor.setAlphaF(m_checked ? Theme::checkedAlpha : (m_isBranch ? Theme::branchAlpha : Theme::normalAlpha));
|
||||
QPen pen(penColor);
|
||||
pen.setWidth(m_isRoot ? (Theme::skeletonNodeBorderSize * 2) : Theme::skeletonNodeBorderSize);
|
||||
setPen(pen);
|
||||
|
||||
QColor brushColor = m_sideColor;
|
||||
brushColor.setAlphaF((m_hovered || m_checked) ? Theme::fillAlpha : 0);
|
||||
QBrush brush(brushColor);
|
||||
setBrush(brush);
|
||||
}
|
||||
|
||||
QColor SkeletonEditNodeItem::nextSideColor()
|
||||
{
|
||||
return Theme::sideColorNameToColorMap[Theme::nextSideColorNameMap[m_sideColorName]];
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
#ifndef SKELETON_EDIT_NODE_ITEM_H
|
||||
#define SKELETON_EDIT_NODE_ITEM_H
|
||||
#include <QGraphicsEllipseItem>
|
||||
#include <map>
|
||||
#include <QString>
|
||||
#include <QColor>
|
||||
|
||||
class SkeletonEditEdgeItem;
|
||||
|
||||
class SkeletonEditNodeItem : public QGraphicsEllipseItem
|
||||
{
|
||||
public:
|
||||
SkeletonEditNodeItem(const QRectF &rect, QGraphicsItem *parent = 0);
|
||||
QPointF origin();
|
||||
void setOrigin(QPointF point);
|
||||
float radius();
|
||||
void setRadius(float radius);
|
||||
bool hovered();
|
||||
void setHovered(bool hovered);
|
||||
bool checked();
|
||||
void setChecked(bool checked);
|
||||
void markAsBranch(bool isBranch);
|
||||
bool isBranch();
|
||||
void markAsRoot(bool isRoot);
|
||||
bool isRoot();
|
||||
SkeletonEditNodeItem *nextSidePair();
|
||||
void setNextSidePair(SkeletonEditNodeItem *nodeItem);
|
||||
const QColor &sideColor();
|
||||
QColor nextSideColor();
|
||||
const QString &sideColorName();
|
||||
QString nextSideColorName();
|
||||
void setSideColorName(const QString &name);
|
||||
private:
|
||||
bool m_hovered;
|
||||
bool m_checked;
|
||||
SkeletonEditNodeItem *m_nextSidePair;
|
||||
QColor m_sideColor;
|
||||
QString m_sideColorName;
|
||||
bool m_isBranch;
|
||||
bool m_isRoot;
|
||||
private:
|
||||
void updateAppearance();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,644 @@
|
|||
#include <QDebug>
|
||||
#include <QScrollBar>
|
||||
#include <QGuiApplication>
|
||||
#include <cmath>
|
||||
#include <QtGlobal>
|
||||
#include <algorithm>
|
||||
#include "skeletongraphicswidget.h"
|
||||
#include "theme.h"
|
||||
#include "util.h"
|
||||
|
||||
SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document) :
|
||||
m_document(document),
|
||||
m_turnaroundChanged(false),
|
||||
m_turnaroundLoader(nullptr),
|
||||
m_dragStarted(false),
|
||||
m_cursorNodeItem(nullptr),
|
||||
m_cursorEdgeItem(nullptr),
|
||||
m_checkedNodeItem(nullptr),
|
||||
m_moveStarted(false),
|
||||
m_hoveredNodeItem(nullptr),
|
||||
m_hoveredEdgeItem(nullptr),
|
||||
m_checkedEdgeItem(nullptr),
|
||||
m_lastAddedX(0),
|
||||
m_lastAddedY(0),
|
||||
m_lastAddedZ(0)
|
||||
{
|
||||
setRenderHint(QPainter::Antialiasing, false);
|
||||
setBackgroundBrush(QBrush(QWidget::palette().color(QWidget::backgroundRole()), Qt::SolidPattern));
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
setFrameStyle(QFrame::NoFrame);
|
||||
|
||||
setAlignment(Qt::AlignCenter);
|
||||
|
||||
setScene(new QGraphicsScene());
|
||||
|
||||
m_backgroundItem = new QGraphicsPixmapItem();
|
||||
m_backgroundItem->setOpacity(0.25);
|
||||
scene()->addItem(m_backgroundItem);
|
||||
|
||||
m_cursorNodeItem = new SkeletonGraphicsNodeItem();
|
||||
m_cursorNodeItem->hide();
|
||||
scene()->addItem(m_cursorNodeItem);
|
||||
|
||||
m_cursorEdgeItem = new SkeletonGraphicsEdgeItem();
|
||||
m_cursorEdgeItem->hide();
|
||||
scene()->addItem(m_cursorEdgeItem);
|
||||
|
||||
scene()->setSceneRect(rect());
|
||||
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::updateItems()
|
||||
{
|
||||
for (auto nodeItemIt = nodeItemMap.begin(); nodeItemIt != nodeItemMap.end(); nodeItemIt++) {
|
||||
nodeRadiusChanged(nodeItemIt->first);
|
||||
nodeOriginChanged(nodeItemIt->first);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::canvasResized()
|
||||
{
|
||||
updateTurnaround();
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::turnaroundChanged()
|
||||
{
|
||||
updateTurnaround();
|
||||
setTransform(QTransform());
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::updateTurnaround()
|
||||
{
|
||||
if (m_document->turnaround.isNull())
|
||||
return;
|
||||
|
||||
m_turnaroundChanged = true;
|
||||
if (m_turnaroundLoader)
|
||||
return;
|
||||
|
||||
qDebug() << "Fit turnaround to view size:" << parentWidget()->rect().size();
|
||||
|
||||
m_turnaroundChanged = false;
|
||||
|
||||
QThread *thread = new QThread;
|
||||
m_turnaroundLoader = new TurnaroundLoader(m_document->turnaround,
|
||||
parentWidget()->rect().size());
|
||||
m_turnaroundLoader->moveToThread(thread);
|
||||
connect(thread, SIGNAL(started()), m_turnaroundLoader, SLOT(process()));
|
||||
connect(m_turnaroundLoader, SIGNAL(finished()), this, SLOT(turnaroundImageReady()));
|
||||
connect(m_turnaroundLoader, SIGNAL(finished()), thread, SLOT(quit()));
|
||||
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::turnaroundImageReady()
|
||||
{
|
||||
QImage *backgroundImage = m_turnaroundLoader->takeResultImage();
|
||||
if (backgroundImage && backgroundImage->width() > 0 && backgroundImage->height() > 0) {
|
||||
qDebug() << "Fit turnaround finished with image size:" << backgroundImage->size();
|
||||
setFixedSize(backgroundImage->size());
|
||||
scene()->setSceneRect(rect());
|
||||
m_backgroundItem->setPixmap(QPixmap::fromImage(*backgroundImage));
|
||||
updateItems();
|
||||
} else {
|
||||
qDebug() << "Fit turnaround failed";
|
||||
}
|
||||
delete backgroundImage;
|
||||
delete m_turnaroundLoader;
|
||||
m_turnaroundLoader = nullptr;
|
||||
|
||||
if (m_turnaroundChanged) {
|
||||
updateTurnaround();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::updateCursor()
|
||||
{
|
||||
if (SkeletonDocumentEditMode::Add != m_document->editMode) {
|
||||
m_cursorEdgeItem->hide();
|
||||
m_cursorNodeItem->hide();
|
||||
}
|
||||
|
||||
switch (m_document->editMode) {
|
||||
case SkeletonDocumentEditMode::Add:
|
||||
setCursor(QCursor(Theme::awesome()->icon(fa::plus).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));
|
||||
break;
|
||||
case SkeletonDocumentEditMode::Select:
|
||||
setCursor(QCursor(Theme::awesome()->icon(fa::mousepointer).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));
|
||||
break;
|
||||
case SkeletonDocumentEditMode::Drag:
|
||||
setCursor(QCursor(Theme::awesome()->icon(m_dragStarted ? fa::handrocko : fa::handpapero).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));
|
||||
break;
|
||||
case SkeletonDocumentEditMode::ZoomIn:
|
||||
setCursor(QCursor(Theme::awesome()->icon(fa::searchplus).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));
|
||||
break;
|
||||
case SkeletonDocumentEditMode::ZoomOut:
|
||||
setCursor(QCursor(Theme::awesome()->icon(fa::searchminus).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));
|
||||
break;
|
||||
}
|
||||
|
||||
emit cursorChanged();
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::editModeChanged()
|
||||
{
|
||||
if (m_checkedNodeItem) {
|
||||
if (!m_checkedNodeItem->checked()) {
|
||||
if (m_hoveredNodeItem == m_checkedNodeItem) {
|
||||
m_checkedNodeItem->setHovered(false);
|
||||
m_hoveredNodeItem = nullptr;
|
||||
}
|
||||
m_checkedNodeItem = nullptr;
|
||||
}
|
||||
}
|
||||
updateCursor();
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
QGraphicsView::mouseMoveEvent(event);
|
||||
mouseMove(event);
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
if (SkeletonDocumentEditMode::ZoomIn == m_document->editMode ||
|
||||
SkeletonDocumentEditMode::ZoomOut == m_document->editMode ||
|
||||
SkeletonDocumentEditMode::Drag == m_document->editMode)
|
||||
QGraphicsView::wheelEvent(event);
|
||||
wheel(event);
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
QGraphicsView::mouseReleaseEvent(event);
|
||||
mouseRelease(event);
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
QGraphicsView::mousePressEvent(event);
|
||||
mousePress(event);
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
QGraphicsView::mouseDoubleClickEvent(event);
|
||||
mouseDoubleClick(event);
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
QGraphicsView::keyPressEvent(event);
|
||||
keyPress(event);
|
||||
}
|
||||
|
||||
bool SkeletonGraphicsWidget::mouseMove(QMouseEvent *event)
|
||||
{
|
||||
if (m_dragStarted) {
|
||||
QPoint currentGlobalPos = event->globalPos();
|
||||
if (verticalScrollBar())
|
||||
verticalScrollBar()->setValue(verticalScrollBar()->value() + m_lastGlobalPos.y() - currentGlobalPos.y());
|
||||
if (horizontalScrollBar())
|
||||
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + m_lastGlobalPos.x() - currentGlobalPos.x());
|
||||
m_lastGlobalPos = currentGlobalPos;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SkeletonDocumentEditMode::Select == m_document->editMode ||
|
||||
SkeletonDocumentEditMode::Add == m_document->editMode) {
|
||||
SkeletonGraphicsNodeItem *newHoverNodeItem = nullptr;
|
||||
SkeletonGraphicsEdgeItem *newHoverEdgeItem = nullptr;
|
||||
QList<QGraphicsItem *> items = scene()->items(mouseEventScenePos(event));
|
||||
for (auto it = items.begin(); it != items.end(); it++) {
|
||||
QGraphicsItem *item = *it;
|
||||
if (item->data(0) == "node") {
|
||||
newHoverNodeItem = (SkeletonGraphicsNodeItem *)item;
|
||||
break;
|
||||
} else if (item->data(0) == "edge") {
|
||||
newHoverEdgeItem = (SkeletonGraphicsEdgeItem *)item;
|
||||
}
|
||||
}
|
||||
if (newHoverNodeItem) {
|
||||
newHoverEdgeItem = nullptr;
|
||||
}
|
||||
if (newHoverNodeItem != m_hoveredNodeItem) {
|
||||
if (nullptr != m_hoveredNodeItem) {
|
||||
m_hoveredNodeItem->setHovered(false);
|
||||
}
|
||||
m_hoveredNodeItem = newHoverNodeItem;
|
||||
if (nullptr != m_hoveredNodeItem) {
|
||||
m_hoveredNodeItem->setHovered(true);
|
||||
}
|
||||
}
|
||||
if (newHoverEdgeItem != m_hoveredEdgeItem) {
|
||||
if (nullptr != m_hoveredEdgeItem) {
|
||||
m_hoveredEdgeItem->setHovered(false);
|
||||
}
|
||||
m_hoveredEdgeItem = newHoverEdgeItem;
|
||||
if (nullptr != m_hoveredEdgeItem) {
|
||||
m_hoveredEdgeItem->setHovered(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SkeletonDocumentEditMode::Add == m_document->editMode) {
|
||||
QPointF mouseScenePos = mouseEventScenePos(event);
|
||||
m_cursorNodeItem->setOrigin(mouseScenePos);
|
||||
if (!m_cursorNodeItem->isVisible()) {
|
||||
m_cursorNodeItem->show();
|
||||
}
|
||||
if (m_checkedNodeItem) {
|
||||
m_cursorEdgeItem->setEndpoints(m_checkedNodeItem, m_cursorNodeItem);
|
||||
if (!m_cursorEdgeItem->isVisible()) {
|
||||
m_cursorEdgeItem->show();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SkeletonDocumentEditMode::Select == m_document->editMode) {
|
||||
if (m_moveStarted && m_checkedNodeItem) {
|
||||
QPointF mouseScenePos = mouseEventScenePos(event);
|
||||
float byX = sceneRadiusToUnified(mouseScenePos.x() - m_lastScenePos.x());
|
||||
float byY = sceneRadiusToUnified(mouseScenePos.y() - m_lastScenePos.y());
|
||||
if (SkeletonProfile::Main == m_checkedNodeItem->profile()) {
|
||||
emit moveNodeBy(m_checkedNodeItem->id(), byX, byY, 0);
|
||||
} else {
|
||||
emit moveNodeBy(m_checkedNodeItem->id(), 0, byY, byX);
|
||||
}
|
||||
m_lastScenePos = mouseScenePos;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SkeletonGraphicsWidget::wheel(QWheelEvent *event)
|
||||
{
|
||||
qreal delta = event->delta() / 10;
|
||||
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) {
|
||||
if (delta > 0)
|
||||
delta = 1;
|
||||
else
|
||||
delta = -1;
|
||||
} else {
|
||||
if (fabs(delta) < 1)
|
||||
delta = delta < 0 ? -1.0 : 1.0;
|
||||
}
|
||||
if (SkeletonDocumentEditMode::Add == m_document->editMode) {
|
||||
if (m_cursorNodeItem->isVisible()) {
|
||||
m_cursorNodeItem->setRadius(m_cursorNodeItem->radius() + delta);
|
||||
return true;
|
||||
}
|
||||
} else if (SkeletonDocumentEditMode::Select == m_document->editMode) {
|
||||
if (m_hoveredNodeItem) {
|
||||
emit scaleNodeByAddRadius(m_hoveredNodeItem->id(), sceneRadiusToUnified(delta));
|
||||
return true;
|
||||
} else if (m_checkedNodeItem) {
|
||||
emit scaleNodeByAddRadius(m_checkedNodeItem->id(), sceneRadiusToUnified(delta));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SkeletonGraphicsWidget::mouseRelease(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
bool processed = m_dragStarted || m_moveStarted;
|
||||
if (m_dragStarted) {
|
||||
m_dragStarted = false;
|
||||
updateCursor();
|
||||
}
|
||||
if (m_moveStarted) {
|
||||
m_moveStarted = false;
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QPointF SkeletonGraphicsWidget::mouseEventScenePos(QMouseEvent *event)
|
||||
{
|
||||
return mapToScene(mapFromGlobal(event->globalPos()));
|
||||
}
|
||||
|
||||
bool SkeletonGraphicsWidget::mousePress(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (SkeletonDocumentEditMode::ZoomIn == m_document->editMode) {
|
||||
ViewportAnchor lastAnchor = transformationAnchor();
|
||||
setTransformationAnchor(QGraphicsView::AnchorUnderMouse) ;
|
||||
scale(1.5, 1.5);
|
||||
setTransformationAnchor(lastAnchor);
|
||||
return true;
|
||||
} else if (SkeletonDocumentEditMode::ZoomOut == m_document->editMode) {
|
||||
ViewportAnchor lastAnchor = transformationAnchor();
|
||||
setTransformationAnchor(QGraphicsView::AnchorUnderMouse) ;
|
||||
scale(0.5, 0.5);
|
||||
setTransformationAnchor(lastAnchor);
|
||||
if ((!verticalScrollBar() || !verticalScrollBar()->isVisible()) &&
|
||||
(!horizontalScrollBar() || !horizontalScrollBar()->isVisible())) {
|
||||
setTransform(QTransform());
|
||||
}
|
||||
return true;
|
||||
} else if (SkeletonDocumentEditMode::Drag == m_document->editMode) {
|
||||
if (!m_dragStarted) {
|
||||
m_lastGlobalPos = event->globalPos();
|
||||
m_dragStarted = true;
|
||||
updateCursor();
|
||||
}
|
||||
} else if (SkeletonDocumentEditMode::Add == m_document->editMode) {
|
||||
if (m_cursorNodeItem->isVisible()) {
|
||||
if (m_checkedNodeItem) {
|
||||
if (m_hoveredNodeItem && m_checkedNodeItem &&
|
||||
m_hoveredNodeItem != m_checkedNodeItem &&
|
||||
m_hoveredNodeItem->profile() == m_checkedNodeItem->profile()) {
|
||||
if (m_document->findEdgeByNodes(m_checkedNodeItem->id(), m_hoveredNodeItem->id()))
|
||||
return true;
|
||||
emit addEdge(m_checkedNodeItem->id(), m_hoveredNodeItem->id());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
QPointF mainProfile = m_cursorNodeItem->origin();
|
||||
QPointF sideProfile = mainProfile;
|
||||
if (mainProfile.x() >= scene()->width() / 2) {
|
||||
sideProfile.setX(mainProfile.x() - scene()->width() / 4);
|
||||
} else {
|
||||
sideProfile.setX(mainProfile.x() +scene()->width() / 4);
|
||||
}
|
||||
QPointF unifiedMainPos = scenePosToUnified(mainProfile);
|
||||
QPointF unifiedSidePos = scenePosToUnified(sideProfile);
|
||||
if (isFloatEqual(m_lastAddedX, unifiedMainPos.x()) && isFloatEqual(m_lastAddedY, unifiedMainPos.y()) && isFloatEqual(m_lastAddedZ, unifiedSidePos.x()))
|
||||
return true;
|
||||
m_lastAddedX = unifiedMainPos.x();
|
||||
m_lastAddedY = unifiedMainPos.y();
|
||||
m_lastAddedZ = unifiedSidePos.x();
|
||||
qDebug() << "Emit add node " << m_lastAddedX << m_lastAddedY << m_lastAddedZ;
|
||||
emit addNode(unifiedMainPos.x(), unifiedMainPos.y(), unifiedSidePos.x(), sceneRadiusToUnified(m_cursorNodeItem->radius()), nullptr == m_checkedNodeItem ? QUuid() : m_checkedNodeItem->id());
|
||||
return true;
|
||||
}
|
||||
} else if (SkeletonDocumentEditMode::Select == m_document->editMode) {
|
||||
bool processed = false;
|
||||
if (m_hoveredNodeItem) {
|
||||
if (m_checkedNodeItem != m_hoveredNodeItem) {
|
||||
if (m_checkedNodeItem) {
|
||||
emit uncheckNode(m_checkedNodeItem->id());
|
||||
m_checkedNodeItem->setChecked(false);
|
||||
}
|
||||
m_checkedNodeItem = m_hoveredNodeItem;
|
||||
m_checkedNodeItem->setChecked(true);
|
||||
emit checkNode(m_checkedNodeItem->id());
|
||||
}
|
||||
m_moveStarted = true;
|
||||
m_lastScenePos = mouseEventScenePos(event);
|
||||
processed = true;
|
||||
} else {
|
||||
if (m_checkedNodeItem) {
|
||||
m_checkedNodeItem->setChecked(false);
|
||||
emit uncheckNode(m_checkedNodeItem->id());
|
||||
m_checkedNodeItem = nullptr;
|
||||
processed = true;
|
||||
}
|
||||
}
|
||||
if (m_hoveredEdgeItem) {
|
||||
if (m_checkedEdgeItem != m_hoveredEdgeItem) {
|
||||
if (m_checkedEdgeItem) {
|
||||
emit uncheckEdge(m_checkedEdgeItem->id());
|
||||
m_checkedEdgeItem->setChecked(false);
|
||||
}
|
||||
m_checkedEdgeItem = m_hoveredEdgeItem;
|
||||
m_checkedEdgeItem->setChecked(true);
|
||||
emit checkEdge(m_checkedEdgeItem->id());
|
||||
}
|
||||
} else {
|
||||
if (m_checkedEdgeItem) {
|
||||
m_checkedEdgeItem->setChecked(false);
|
||||
emit uncheckEdge(m_checkedEdgeItem->id());
|
||||
m_checkedEdgeItem = nullptr;
|
||||
processed = true;
|
||||
}
|
||||
}
|
||||
if (processed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float SkeletonGraphicsWidget::sceneRadiusToUnified(float radius)
|
||||
{
|
||||
if (0 == scene()->height())
|
||||
return 0;
|
||||
return radius / scene()->height();
|
||||
}
|
||||
|
||||
float SkeletonGraphicsWidget::sceneRadiusFromUnified(float radius)
|
||||
{
|
||||
return radius * scene()->height();
|
||||
}
|
||||
|
||||
QPointF SkeletonGraphicsWidget::scenePosToUnified(QPointF pos)
|
||||
{
|
||||
if (0 == scene()->height())
|
||||
return QPointF(0, 0);
|
||||
return QPointF(pos.x() / scene()->height(), pos.y() / scene()->height());
|
||||
}
|
||||
|
||||
QPointF SkeletonGraphicsWidget::scenePosFromUnified(QPointF pos)
|
||||
{
|
||||
return QPointF(pos.x() * scene()->height(), pos.y() * scene()->height());
|
||||
}
|
||||
|
||||
bool SkeletonGraphicsWidget::mouseDoubleClick(QMouseEvent *event)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SkeletonGraphicsWidget::keyPress(QKeyEvent *event)
|
||||
{
|
||||
if (event->key() == Qt::Key_Delete || event->key() ==Qt::Key_Backspace) {
|
||||
bool processed = false;
|
||||
if (m_checkedNodeItem) {
|
||||
emit removeNode(m_checkedNodeItem->id());
|
||||
processed = true;
|
||||
}
|
||||
if (m_checkedEdgeItem) {
|
||||
emit removeEdge(m_checkedEdgeItem->id());
|
||||
processed = true;
|
||||
}
|
||||
if (processed) {
|
||||
return true;
|
||||
}
|
||||
} else if (event->key() == Qt::Key_A) {
|
||||
if (SkeletonDocumentEditMode::Add == m_document->editMode) {
|
||||
emit setEditMode(SkeletonDocumentEditMode::Select);
|
||||
} else {
|
||||
emit setEditMode(SkeletonDocumentEditMode::Add);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::nodeAdded(QUuid nodeId)
|
||||
{
|
||||
const SkeletonNode *node = m_document->findNode(nodeId);
|
||||
if (nullptr == node) {
|
||||
qDebug() << "New node added but node id not exist:" << nodeId;
|
||||
return;
|
||||
}
|
||||
SkeletonGraphicsNodeItem *mainProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Main);
|
||||
SkeletonGraphicsNodeItem *sideProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Side);
|
||||
mainProfileItem->setOrigin(scenePosFromUnified(QPointF(node->x, node->y)));
|
||||
sideProfileItem->setOrigin(scenePosFromUnified(QPointF(node->z, node->y)));
|
||||
mainProfileItem->setRadius(sceneRadiusFromUnified(node->radius));
|
||||
sideProfileItem->setRadius(sceneRadiusFromUnified(node->radius));
|
||||
mainProfileItem->setId(nodeId);
|
||||
sideProfileItem->setId(nodeId);
|
||||
scene()->addItem(mainProfileItem);
|
||||
scene()->addItem(sideProfileItem);
|
||||
nodeItemMap[nodeId] = std::make_pair(mainProfileItem, sideProfileItem);
|
||||
|
||||
if (nullptr == m_checkedNodeItem) {
|
||||
m_checkedNodeItem = mainProfileItem;
|
||||
} else {
|
||||
if (SkeletonProfile::Main == m_checkedNodeItem->profile()) {
|
||||
m_checkedNodeItem = mainProfileItem;
|
||||
} else {
|
||||
m_checkedNodeItem = sideProfileItem;
|
||||
}
|
||||
m_cursorEdgeItem->setEndpoints(m_checkedNodeItem, m_cursorNodeItem);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::edgeAdded(QUuid edgeId)
|
||||
{
|
||||
const SkeletonEdge *edge = m_document->findEdge(edgeId);
|
||||
if (nullptr == edge) {
|
||||
qDebug() << "New edge added but edge id not exist:" << edgeId;
|
||||
return;
|
||||
}
|
||||
if (edge->nodeIds.size() != 2) {
|
||||
qDebug() << "Invalid node count of edge:" << edgeId;
|
||||
return;
|
||||
}
|
||||
QUuid fromNodeId = edge->nodeIds[0];
|
||||
QUuid toNodeId = edge->nodeIds[1];
|
||||
auto fromIt = nodeItemMap.find(fromNodeId);
|
||||
if (fromIt == nodeItemMap.end()) {
|
||||
qDebug() << "Node not found:" << fromNodeId;
|
||||
return;
|
||||
}
|
||||
auto toIt = nodeItemMap.find(toNodeId);
|
||||
if (toIt == nodeItemMap.end()) {
|
||||
qDebug() << "Node not found:" << toNodeId;
|
||||
return;
|
||||
}
|
||||
SkeletonGraphicsEdgeItem *mainProfileEdgeItem = new SkeletonGraphicsEdgeItem();
|
||||
SkeletonGraphicsEdgeItem *sideProfileEdgeItem = new SkeletonGraphicsEdgeItem();
|
||||
mainProfileEdgeItem->setId(edgeId);
|
||||
sideProfileEdgeItem->setId(edgeId);
|
||||
mainProfileEdgeItem->setEndpoints(fromIt->second.first, toIt->second.first);
|
||||
sideProfileEdgeItem->setEndpoints(fromIt->second.second, toIt->second.second);
|
||||
scene()->addItem(mainProfileEdgeItem);
|
||||
scene()->addItem(sideProfileEdgeItem);
|
||||
edgeItemMap[edgeId] = std::make_pair(mainProfileEdgeItem, sideProfileEdgeItem);
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::nodeRemoved(QUuid nodeId)
|
||||
{
|
||||
m_lastAddedX = 0;
|
||||
m_lastAddedY = 0;
|
||||
m_lastAddedZ = 0;
|
||||
auto nodeItemIt = nodeItemMap.find(nodeId);
|
||||
if (nodeItemIt == nodeItemMap.end()) {
|
||||
qDebug() << "Node removed but node id not exist:" << nodeId;
|
||||
return;
|
||||
}
|
||||
if (m_hoveredNodeItem == nodeItemIt->second.first)
|
||||
m_hoveredNodeItem = nullptr;
|
||||
if (m_hoveredNodeItem == nodeItemIt->second.second)
|
||||
m_hoveredNodeItem = nullptr;
|
||||
if (m_checkedNodeItem == nodeItemIt->second.first)
|
||||
m_checkedNodeItem = nullptr;
|
||||
if (m_checkedNodeItem == nodeItemIt->second.second)
|
||||
m_checkedNodeItem = nullptr;
|
||||
delete nodeItemIt->second.first;
|
||||
delete nodeItemIt->second.second;
|
||||
nodeItemMap.erase(nodeItemIt);
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::edgeRemoved(QUuid edgeId)
|
||||
{
|
||||
auto edgeItemIt = edgeItemMap.find(edgeId);
|
||||
if (edgeItemIt == edgeItemMap.end()) {
|
||||
qDebug() << "Edge removed but edge id not exist:" << edgeId;
|
||||
return;
|
||||
}
|
||||
if (m_hoveredEdgeItem == edgeItemIt->second.first)
|
||||
m_hoveredEdgeItem = nullptr;
|
||||
if (m_hoveredEdgeItem == edgeItemIt->second.second)
|
||||
m_hoveredEdgeItem = nullptr;
|
||||
if (m_checkedEdgeItem == edgeItemIt->second.first)
|
||||
m_checkedEdgeItem = nullptr;
|
||||
if (m_checkedEdgeItem == edgeItemIt->second.second)
|
||||
m_checkedEdgeItem = nullptr;
|
||||
delete edgeItemIt->second.first;
|
||||
delete edgeItemIt->second.second;
|
||||
edgeItemMap.erase(edgeItemIt);
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::nodeRadiusChanged(QUuid nodeId)
|
||||
{
|
||||
const SkeletonNode *node = m_document->findNode(nodeId);
|
||||
if (nullptr == node) {
|
||||
qDebug() << "Node changed but node id not exist:" << nodeId;
|
||||
return;
|
||||
}
|
||||
auto it = nodeItemMap.find(nodeId);
|
||||
if (it == nodeItemMap.end()) {
|
||||
qDebug() << "Node not found:" << nodeId;
|
||||
return;
|
||||
}
|
||||
float sceneRadius = sceneRadiusFromUnified(node->radius);
|
||||
it->second.first->setRadius(sceneRadius);
|
||||
it->second.second->setRadius(sceneRadius);
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::nodeOriginChanged(QUuid nodeId)
|
||||
{
|
||||
const SkeletonNode *node = m_document->findNode(nodeId);
|
||||
if (nullptr == node) {
|
||||
qDebug() << "Node changed but node id not exist:" << nodeId;
|
||||
return;
|
||||
}
|
||||
auto it = nodeItemMap.find(nodeId);
|
||||
if (it == nodeItemMap.end()) {
|
||||
qDebug() << "Node not found:" << nodeId;
|
||||
return;
|
||||
}
|
||||
QPointF mainPos = scenePosFromUnified(QPointF(node->x, node->y));
|
||||
QPointF sidePos = scenePosFromUnified(QPointF(node->z, node->y));
|
||||
it->second.first->setOrigin(mainPos);
|
||||
it->second.second->setOrigin(sidePos);
|
||||
for (auto edgeIt = node->edgeIds.begin(); edgeIt != node->edgeIds.end(); edgeIt++) {
|
||||
auto edgeItemIt = edgeItemMap.find(*edgeIt);
|
||||
if (edgeItemIt == edgeItemMap.end()) {
|
||||
qDebug() << "Edge item not found:" << *edgeIt;
|
||||
continue;
|
||||
}
|
||||
edgeItemIt->second.first->updateAppearance();
|
||||
edgeItemIt->second.second->updateAppearance();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::edgeChanged(QUuid edgeId)
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,320 @@
|
|||
#ifndef SKELETON_GRAPHICS_VIEW_H
|
||||
#define SKELETON_GRAPHICS_VIEW_H
|
||||
#include <map>
|
||||
#include <QMouseEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QGraphicsEllipseItem>
|
||||
#include <QGraphicsLineItem>
|
||||
#include <QGraphicsView>
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QThread>
|
||||
#include <cmath>
|
||||
#include "skeletondocument.h"
|
||||
#include "turnaroundloader.h"
|
||||
#include "theme.h"
|
||||
|
||||
class SkeletonGraphicsNodeItem : public QGraphicsEllipseItem
|
||||
{
|
||||
public:
|
||||
SkeletonGraphicsNodeItem(SkeletonProfile profile=SkeletonProfile::Unknown) :
|
||||
m_profile(profile),
|
||||
m_hovered(false),
|
||||
m_checked(false)
|
||||
{
|
||||
setData(0, "node");
|
||||
setRadius(32);
|
||||
}
|
||||
|
||||
void updateAppearance()
|
||||
{
|
||||
QColor color = Theme::white;
|
||||
|
||||
switch (m_profile)
|
||||
{
|
||||
case SkeletonProfile::Unknown:
|
||||
break;
|
||||
case SkeletonProfile::Main:
|
||||
color = Theme::red;
|
||||
break;
|
||||
case SkeletonProfile::Side:
|
||||
color = Theme::green;
|
||||
break;
|
||||
}
|
||||
|
||||
QColor penColor = color;
|
||||
penColor.setAlphaF(m_checked ? Theme::checkedAlpha : Theme::normalAlpha);
|
||||
QPen pen(penColor);
|
||||
pen.setWidth(0);
|
||||
setPen(pen);
|
||||
|
||||
QColor brushColor = color;
|
||||
brushColor.setAlphaF((m_checked || m_hovered) ? Theme::fillAlpha : 0);
|
||||
QBrush brush(brushColor);
|
||||
setBrush(brush);
|
||||
}
|
||||
|
||||
void setOrigin(QPointF point)
|
||||
{
|
||||
QPointF moveBy = point - origin();
|
||||
QRectF newRect = rect();
|
||||
newRect.adjust(moveBy.x(), moveBy.y(), moveBy.x(), moveBy.y());
|
||||
setRect(newRect);
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
QPointF origin()
|
||||
{
|
||||
return QPointF(rect().x() + rect().width() / 2,
|
||||
rect().y() + rect().height() / 2);
|
||||
}
|
||||
|
||||
float radius()
|
||||
{
|
||||
return rect().width() / 2;
|
||||
}
|
||||
|
||||
void setRadius(float radius)
|
||||
{
|
||||
if (radius < 4)
|
||||
radius = 4;
|
||||
QPointF oldOrigin = origin();
|
||||
setRect(oldOrigin.x() - radius, oldOrigin.y() - radius,
|
||||
radius * 2, radius * 2);
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
SkeletonProfile profile()
|
||||
{
|
||||
return m_profile;
|
||||
}
|
||||
|
||||
QUuid id()
|
||||
{
|
||||
return m_uuid;
|
||||
}
|
||||
|
||||
void setId(QUuid id)
|
||||
{
|
||||
m_uuid = id;
|
||||
}
|
||||
|
||||
void setHovered(bool hovered)
|
||||
{
|
||||
m_hovered = hovered;
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
void setChecked(bool checked)
|
||||
{
|
||||
m_checked = checked;
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
bool checked()
|
||||
{
|
||||
return m_checked;
|
||||
}
|
||||
private:
|
||||
QUuid m_uuid;
|
||||
SkeletonProfile m_profile;
|
||||
bool m_hovered;
|
||||
bool m_checked;
|
||||
};
|
||||
|
||||
class SkeletonGraphicsEdgeItem : public QGraphicsPolygonItem
|
||||
{
|
||||
public:
|
||||
SkeletonGraphicsEdgeItem() :
|
||||
m_firstItem(nullptr),
|
||||
m_secondItem(nullptr),
|
||||
m_hovered(false),
|
||||
m_checked(false)
|
||||
{
|
||||
setData(0, "edge");
|
||||
}
|
||||
|
||||
void setEndpoints(SkeletonGraphicsNodeItem *first, SkeletonGraphicsNodeItem *second)
|
||||
{
|
||||
m_firstItem = first;
|
||||
m_secondItem = second;
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
void updateAppearance()
|
||||
{
|
||||
if (nullptr == m_firstItem || nullptr == m_secondItem)
|
||||
return;
|
||||
|
||||
QLineF line(m_firstItem->origin(), m_secondItem->origin());
|
||||
|
||||
QPolygonF polygon;
|
||||
float radAngle = line.angle() * M_PI / 180;
|
||||
float dx = 5 * sin(radAngle);
|
||||
float dy = 5 * cos(radAngle);
|
||||
QPointF offset1 = QPointF(dx, dy);
|
||||
QPointF offset2 = QPointF(-dx, -dy);
|
||||
polygon << line.p1() + offset1 << line.p1() + offset2 << line.p2() + offset2 << line.p2() + offset1;
|
||||
setPolygon(polygon);
|
||||
|
||||
QColor color = Theme::white;
|
||||
|
||||
switch (m_firstItem->profile())
|
||||
{
|
||||
case SkeletonProfile::Unknown:
|
||||
break;
|
||||
case SkeletonProfile::Main:
|
||||
color = Theme::red;
|
||||
break;
|
||||
case SkeletonProfile::Side:
|
||||
color = Theme::green;
|
||||
break;
|
||||
}
|
||||
|
||||
QColor penColor = color;
|
||||
penColor.setAlphaF((m_checked || m_hovered) ? Theme::checkedAlpha : Theme::normalAlpha);
|
||||
QPen pen(penColor);
|
||||
pen.setWidth(0);
|
||||
setPen(pen);
|
||||
}
|
||||
|
||||
QUuid id()
|
||||
{
|
||||
return m_uuid;
|
||||
}
|
||||
|
||||
void setId(QUuid id)
|
||||
{
|
||||
m_uuid = id;
|
||||
}
|
||||
|
||||
void setHovered(bool hovered)
|
||||
{
|
||||
m_hovered = hovered;
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
void setChecked(bool checked)
|
||||
{
|
||||
m_checked = checked;
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
private:
|
||||
QUuid m_uuid;
|
||||
SkeletonGraphicsNodeItem *m_firstItem;
|
||||
SkeletonGraphicsNodeItem *m_secondItem;
|
||||
QPolygonF m_selectionPolygon;
|
||||
bool m_hovered;
|
||||
bool m_checked;
|
||||
};
|
||||
|
||||
class SkeletonGraphicsFunctions
|
||||
{
|
||||
public:
|
||||
virtual bool mouseMove(QMouseEvent *event) = 0;
|
||||
virtual bool wheel(QWheelEvent *event) = 0;
|
||||
virtual bool mouseRelease(QMouseEvent *event) = 0;
|
||||
virtual bool mousePress(QMouseEvent *event) = 0;
|
||||
virtual bool mouseDoubleClick(QMouseEvent *event) = 0;
|
||||
virtual bool keyPress(QKeyEvent *event) = 0;
|
||||
};
|
||||
|
||||
class SkeletonGraphicsWidget : public QGraphicsView, public SkeletonGraphicsFunctions
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void addNode(float x, float y, float z, float radius, QUuid fromNodeId);
|
||||
void uncheckNode(QUuid nodeId);
|
||||
void checkNode(QUuid nodeId);
|
||||
void uncheckEdge(QUuid edgeId);
|
||||
void checkEdge(QUuid edgeId);
|
||||
void scaleNodeByAddRadius(QUuid nodeId, float amount);
|
||||
void moveNodeBy(QUuid nodeId, float x, float y, float z);
|
||||
void removeNode(QUuid nodeId);
|
||||
void setEditMode(SkeletonDocumentEditMode mode);
|
||||
void removeEdge(QUuid edgeId);
|
||||
void addEdge(QUuid fromNodeId, QUuid toNodeId);
|
||||
void cursorChanged();
|
||||
public:
|
||||
SkeletonGraphicsWidget(const SkeletonDocument *document);
|
||||
std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> nodeItemMap;
|
||||
std::map<QUuid, std::pair<SkeletonGraphicsEdgeItem *, SkeletonGraphicsEdgeItem *>> edgeItemMap;
|
||||
bool mouseMove(QMouseEvent *event);
|
||||
bool wheel(QWheelEvent *event);
|
||||
bool mouseRelease(QMouseEvent *event);
|
||||
bool mousePress(QMouseEvent *event);
|
||||
bool mouseDoubleClick(QMouseEvent *event);
|
||||
bool keyPress(QKeyEvent *event);
|
||||
protected:
|
||||
void mouseMoveEvent(QMouseEvent *event);
|
||||
void wheelEvent(QWheelEvent *event);
|
||||
void mouseReleaseEvent(QMouseEvent *event);
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
void mouseDoubleClickEvent(QMouseEvent *event);
|
||||
void keyPressEvent(QKeyEvent *event);
|
||||
public slots:
|
||||
void nodeAdded(QUuid nodeId);
|
||||
void edgeAdded(QUuid edgeId);
|
||||
void nodeRemoved(QUuid nodeId);
|
||||
void edgeRemoved(QUuid edgeId);
|
||||
void nodeRadiusChanged(QUuid nodeId);
|
||||
void nodeOriginChanged(QUuid nodeId);
|
||||
void edgeChanged(QUuid edgeId);
|
||||
void turnaroundChanged();
|
||||
void canvasResized();
|
||||
void editModeChanged();
|
||||
void updateCursor();
|
||||
private slots:
|
||||
void turnaroundImageReady();
|
||||
private:
|
||||
QPointF mouseEventScenePos(QMouseEvent *event);
|
||||
QPointF scenePosToUnified(QPointF pos);
|
||||
QPointF scenePosFromUnified(QPointF pos);
|
||||
float sceneRadiusToUnified(float radius);
|
||||
float sceneRadiusFromUnified(float radius);
|
||||
void updateTurnaround();
|
||||
void updateItems();
|
||||
private:
|
||||
QGraphicsPixmapItem *m_backgroundItem;
|
||||
const SkeletonDocument *m_document;
|
||||
bool m_turnaroundChanged;
|
||||
TurnaroundLoader *m_turnaroundLoader;
|
||||
bool m_dragStarted;
|
||||
QPoint m_lastGlobalPos;
|
||||
bool m_moveStarted;
|
||||
QPointF m_lastScenePos;
|
||||
SkeletonGraphicsNodeItem *m_cursorNodeItem;
|
||||
SkeletonGraphicsEdgeItem *m_cursorEdgeItem;
|
||||
SkeletonGraphicsNodeItem *m_checkedNodeItem;
|
||||
SkeletonGraphicsNodeItem *m_hoveredNodeItem;
|
||||
SkeletonGraphicsEdgeItem *m_hoveredEdgeItem;
|
||||
SkeletonGraphicsEdgeItem *m_checkedEdgeItem;
|
||||
float m_lastAddedX;
|
||||
float m_lastAddedY;
|
||||
float m_lastAddedZ;
|
||||
};
|
||||
|
||||
class SkeletonGraphicsContainerWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void containerSizeChanged(QSize size);
|
||||
public:
|
||||
SkeletonGraphicsContainerWidget() :
|
||||
m_graphicsWidget(nullptr)
|
||||
{
|
||||
}
|
||||
void resizeEvent(QResizeEvent *event) override
|
||||
{
|
||||
if (m_graphicsWidget && m_graphicsWidget->size() != event->size())
|
||||
emit containerSizeChanged(event->size());
|
||||
}
|
||||
void setGraphicsWidget(SkeletonGraphicsWidget *graphicsWidget)
|
||||
{
|
||||
m_graphicsWidget = graphicsWidget;
|
||||
}
|
||||
private:
|
||||
SkeletonGraphicsWidget *m_graphicsWidget;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,50 @@
|
|||
#include <QFormLayout>
|
||||
#include "skeletonnodepropertywidget.h"
|
||||
|
||||
SkeletonNodePropertyWidget::SkeletonNodePropertyWidget(const SkeletonDocument *document) :
|
||||
m_rootMarkModeComboBox(nullptr),
|
||||
m_document(document)
|
||||
{
|
||||
m_rootMarkModeComboBox = new QComboBox;
|
||||
|
||||
m_rootMarkModeComboBox->insertItem(0, tr("Auto"), (int)SkeletonNodeRootMarkMode::Auto);
|
||||
m_rootMarkModeComboBox->insertItem(1, tr("Mark as Root"), (int)SkeletonNodeRootMarkMode::MarkAsRoot);
|
||||
m_rootMarkModeComboBox->insertItem(2, tr("Mark as Not Root"), (int)SkeletonNodeRootMarkMode::MarkAsNotRoot);
|
||||
|
||||
QFormLayout *formLayout = new QFormLayout;
|
||||
formLayout->addRow(tr("Root Mark:"), m_rootMarkModeComboBox);
|
||||
|
||||
setLayout(formLayout);
|
||||
|
||||
hide();
|
||||
|
||||
connect(m_rootMarkModeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [=](int index) {
|
||||
if (-1 != index) {
|
||||
int mode = m_rootMarkModeComboBox->itemData(index).toInt();
|
||||
emit setNodeRootMarkMode(m_nodeId, static_cast<SkeletonNodeRootMarkMode>(mode));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SkeletonNodePropertyWidget::showProperties(QUuid nodeId)
|
||||
{
|
||||
m_nodeId = nodeId;
|
||||
updateData();
|
||||
show();
|
||||
}
|
||||
|
||||
void SkeletonNodePropertyWidget::updateData()
|
||||
{
|
||||
const SkeletonNode *node = m_document->findNode(m_nodeId);
|
||||
if (nullptr == node) {
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
int selectIndex = m_rootMarkModeComboBox->findData((int)node->rootMarkMode);
|
||||
m_rootMarkModeComboBox->setCurrentIndex(selectIndex);
|
||||
}
|
||||
|
||||
QUuid SkeletonNodePropertyWidget::currentNodeId()
|
||||
{
|
||||
return m_nodeId;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef SKELETON_NODE_PROPERTY_WIDGET_H
|
||||
#define SKELETON_NODE_PROPERTY_WIDGET_H
|
||||
#include <QWidget>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QComboBox>
|
||||
#include "skeletondocument.h"
|
||||
|
||||
class SkeletonNodePropertyWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void setNodeRootMarkMode(QUuid nodeId, SkeletonNodeRootMarkMode mode);
|
||||
public slots:
|
||||
void showProperties(QUuid nodeId);
|
||||
public:
|
||||
SkeletonNodePropertyWidget(const SkeletonDocument *document);
|
||||
QUuid currentNodeId();
|
||||
private:
|
||||
void updateData();
|
||||
private:
|
||||
QComboBox *m_rootMarkModeComboBox;
|
||||
const SkeletonDocument *m_document;
|
||||
QUuid m_nodeId;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,89 @@
|
|||
#include <QGridLayout>
|
||||
#include <QDebug>
|
||||
#include "skeletonpartlistwidget.h"
|
||||
#include "theme.h"
|
||||
|
||||
SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid partId) :
|
||||
m_document(document),
|
||||
m_partId(partId)
|
||||
{
|
||||
m_visibleButton = new QPushButton(QChar(fa::eye));
|
||||
//m_visibleButton->setStyleSheet("QPushButton {border: none; background: none;}");
|
||||
m_visibleButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize * 2 / 3));
|
||||
m_visibleButton->setFixedSize(Theme::toolIconSize * 2 / 3, Theme::toolIconSize * 2 / 3);
|
||||
|
||||
m_previewLabel = new QLabel;
|
||||
|
||||
//m_nameLabel = new QLabel;
|
||||
|
||||
QGridLayout *mainLayout = new QGridLayout;
|
||||
mainLayout->addWidget(m_visibleButton, 0, 0);
|
||||
mainLayout->addWidget(m_previewLabel, 0, 1);
|
||||
//mainLayout->addWidget(m_nameLabel, 0, 2);
|
||||
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
QLabel *SkeletonPartWidget::previewLabel()
|
||||
{
|
||||
return m_previewLabel;
|
||||
}
|
||||
|
||||
void SkeletonPartWidget::reload()
|
||||
{
|
||||
const SkeletonPart *part = m_document->findPart(m_partId);
|
||||
if (!part) {
|
||||
qDebug() << "Part not found:" << m_partId;
|
||||
return;
|
||||
}
|
||||
//m_nameLabel->setText(part->name.isEmpty() ? part->id.toString() : part->name);
|
||||
m_previewLabel->setPixmap(QPixmap::fromImage(part->preview));
|
||||
}
|
||||
|
||||
SkeletonPartListWidget::SkeletonPartListWidget(const SkeletonDocument *document) :
|
||||
m_document(document)
|
||||
{
|
||||
setSelectionMode(QAbstractItemView::NoSelection);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
}
|
||||
|
||||
void SkeletonPartListWidget::partChanged(QUuid partId)
|
||||
{
|
||||
auto itemIt = m_itemMap.find(partId);
|
||||
if (itemIt == m_itemMap.end()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonPartListWidget::partListChanged()
|
||||
{
|
||||
clear();
|
||||
m_itemMap.clear();
|
||||
|
||||
for (auto partIdIt = m_document->partIds.begin(); partIdIt != m_document->partIds.end(); partIdIt++) {
|
||||
QUuid partId = *partIdIt;
|
||||
QListWidgetItem *item = new QListWidgetItem(this);
|
||||
item->setSizeHint(QSize(width(), Theme::previewImageSize));
|
||||
addItem(item);
|
||||
SkeletonPartWidget *widget = new SkeletonPartWidget(m_document, partId);
|
||||
setItemWidget(item, widget);
|
||||
widget->reload();
|
||||
m_itemMap[partId] = item;
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonPartListWidget::partPreviewChanged(QUuid partid)
|
||||
{
|
||||
const SkeletonPart *part = m_document->findPart(partid);
|
||||
if (!part) {
|
||||
qDebug() << "Part not found:" << partid;
|
||||
return;
|
||||
}
|
||||
auto item = m_itemMap.find(partid);
|
||||
if (item == m_itemMap.end()) {
|
||||
qDebug() << "Part item not found:" << partid;
|
||||
return;
|
||||
}
|
||||
SkeletonPartWidget *widget = (SkeletonPartWidget *)itemWidget(item->second);
|
||||
widget->previewLabel()->setPixmap(QPixmap::fromImage(part->preview));
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef SKELETON_PART_LIST_WIDGET_H
|
||||
#define SKELETON_PART_LIST_WIDGET_H
|
||||
#include <QListWidget>
|
||||
#include <map>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include "skeletondocument.h"
|
||||
|
||||
class SkeletonPartWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SkeletonPartWidget(const SkeletonDocument *document, QUuid partId);
|
||||
void reload();
|
||||
QLabel *previewLabel();
|
||||
private:
|
||||
const SkeletonDocument *m_document;
|
||||
QUuid m_partId;
|
||||
QLabel *m_previewLabel;
|
||||
QPushButton *m_visibleButton;
|
||||
QLabel *m_nameLabel;
|
||||
};
|
||||
|
||||
class SkeletonPartListWidget : public QListWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SkeletonPartListWidget(const SkeletonDocument *document);
|
||||
public slots:
|
||||
void partChanged(QUuid partId);
|
||||
void partListChanged();
|
||||
void partPreviewChanged(QUuid partid);
|
||||
private:
|
||||
const SkeletonDocument *m_document;
|
||||
std::map<QUuid, QListWidgetItem *> m_itemMap;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -12,6 +12,8 @@ public:
|
|||
std::map<QString, QString> canvas;
|
||||
std::map<QString, std::map<QString, QString>> nodes;
|
||||
std::map<QString, std::map<QString, QString>> edges;
|
||||
std::map<QString, std::map<QString, QString>> parts;
|
||||
std::vector<QString> partIdList;
|
||||
public:
|
||||
SkeletonSnapshot();
|
||||
void splitByConnectivity(std::vector<SkeletonSnapshot> *groups);
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
#include "skeletontomesh.h"
|
||||
#include "meshlite.h"
|
||||
#include "skeletoneditnodeitem.h"
|
||||
#include "skeletoneditedgeitem.h"
|
||||
#include "skeletonsnapshot.h"
|
||||
#include "unionmesh.h"
|
||||
#include <vector>
|
||||
|
||||
struct NodeItemInfo
|
||||
{
|
||||
int index;
|
||||
int neighborCount;
|
||||
};
|
||||
|
||||
SkeletonToMesh::SkeletonToMesh(SkeletonSnapshot *snapshot) :
|
||||
m_mesh(NULL),
|
||||
m_snapshot(*snapshot)
|
||||
{
|
||||
}
|
||||
|
||||
SkeletonToMesh::~SkeletonToMesh()
|
||||
{
|
||||
delete m_mesh;
|
||||
}
|
||||
|
||||
Mesh *SkeletonToMesh::takeResultMesh()
|
||||
{
|
||||
Mesh *mesh = m_mesh;
|
||||
m_mesh = NULL;
|
||||
return mesh;
|
||||
}
|
||||
|
||||
#if USE_CARVE == 1
|
||||
#define ExternalMesh carve::poly::Polyhedron
|
||||
#define makeExternalMeshFromMeshlite makeCarveMeshFromMeshlite
|
||||
#define unionExternalMeshs unionCarveMeshs
|
||||
#define makeMeshliteMeshFromExternal makeMeshliteMeshFromCarve
|
||||
#endif
|
||||
|
||||
#if USE_CGAL == 1
|
||||
#define ExternalMesh CgalMesh
|
||||
#define makeExternalMeshFromMeshlite makeCgalMeshFromMeshlite
|
||||
#define unionExternalMeshs unionCgalMeshs
|
||||
#define makeMeshliteMeshFromExternal makeMeshliteMeshFromCgal
|
||||
#endif
|
||||
|
||||
void SkeletonToMesh::process()
|
||||
{
|
||||
std::vector<SkeletonSnapshot> groups;
|
||||
QRectF globalFront = m_snapshot.boundingBoxFront();
|
||||
QRectF globalSide = m_snapshot.boundingBoxSide();
|
||||
QString rootNodeId = m_snapshot.rootNode();
|
||||
printf("rootNodeId:%s\n", rootNodeId.toUtf8().constData());
|
||||
float frontMiddleX = m_snapshot.nodes[rootNodeId]["x"].toFloat();
|
||||
float frontMiddleY = m_snapshot.nodes[rootNodeId]["y"].toFloat();
|
||||
float sideMiddleX = m_snapshot.nodes[m_snapshot.nodes[rootNodeId]["nextSidePair"]]["x"].toFloat();
|
||||
bool combineEnabled = "true" == m_snapshot.canvas["combine"];
|
||||
bool unionEnabled = "true" == m_snapshot.canvas["union"];
|
||||
bool subdivEnabled = "true" == m_snapshot.canvas["subdiv"];
|
||||
m_snapshot.splitByConnectivity(&groups);
|
||||
|
||||
std::vector<int> meshIds;
|
||||
void *meshliteContext = meshlite_create_context();
|
||||
for (size_t i = 0; i < groups.size(); i++) {
|
||||
SkeletonSnapshot *skeleton = &groups[i];
|
||||
//QRectF front = skeleton->boundingBoxFront();
|
||||
//QRectF side = skeleton->boundingBoxSide();
|
||||
//float canvasWidth = skeleton->canvas["width"].toFloat();
|
||||
//float canvasHeight = skeleton->canvas["height"].toFloat();
|
||||
float canvasHeight = globalFront.height();
|
||||
|
||||
std::map<QString, int> bmeshNodeMap;
|
||||
|
||||
std::map<QString, std::map<QString, QString>>::iterator nodeIterator;
|
||||
int bmeshId = meshlite_bmesh_create(meshliteContext);
|
||||
for (nodeIterator = skeleton->nodes.begin(); nodeIterator != skeleton->nodes.end(); nodeIterator++) {
|
||||
if ("red" != nodeIterator->second["sideColorName"])
|
||||
continue;
|
||||
std::map<QString, std::map<QString, QString>>::iterator nextSidePair = skeleton->nodes.find(nodeIterator->second["nextSidePair"]);
|
||||
if (nextSidePair == skeleton->nodes.end())
|
||||
continue;
|
||||
float x = (nodeIterator->second["x"].toFloat() - frontMiddleX) / canvasHeight;
|
||||
float y = (nodeIterator->second["y"].toFloat() - frontMiddleY) / canvasHeight;
|
||||
float z = (nextSidePair->second["x"].toFloat() - sideMiddleX) / canvasHeight;
|
||||
float r = nodeIterator->second["radius"].toFloat() / canvasHeight;
|
||||
int bmeshNodeId = meshlite_bmesh_add_node(meshliteContext, bmeshId, x, y, z, r);
|
||||
printf("meshlite_bmesh_add_node x:%f y:%f z:%f r:%f nodeName:%s bmeshNodeId:%d\n", x, y, z, r, nodeIterator->first.toUtf8().constData(), bmeshNodeId);
|
||||
bmeshNodeMap[nodeIterator->first] = bmeshNodeId;
|
||||
}
|
||||
|
||||
std::map<QString, std::map<QString, QString>>::iterator edgeIterator;
|
||||
for (edgeIterator = skeleton->edges.begin(); edgeIterator != skeleton->edges.end(); edgeIterator++) {
|
||||
std::map<QString, int>::iterator from = bmeshNodeMap.find(edgeIterator->second["from"]);
|
||||
if (from == bmeshNodeMap.end())
|
||||
continue;
|
||||
std::map<QString, int>::iterator to = bmeshNodeMap.find(edgeIterator->second["to"]);
|
||||
if (to == bmeshNodeMap.end())
|
||||
continue;
|
||||
printf("meshlite_bmesh_add_edge %d -> %d\n", from->second, to->second);
|
||||
meshlite_bmesh_add_edge(meshliteContext, bmeshId, from->second, to->second);
|
||||
}
|
||||
|
||||
if (bmeshNodeMap.size() > 0 && !skeleton->rootNode().isEmpty()) {
|
||||
int meshId = meshlite_bmesh_generate_mesh(meshliteContext, bmeshId, bmeshNodeMap[skeleton->rootNode()]);
|
||||
meshIds.push_back(meshId);
|
||||
}
|
||||
|
||||
meshlite_bmesh_destroy(meshliteContext, bmeshId);
|
||||
}
|
||||
|
||||
if (meshIds.size() > 0) {
|
||||
int mergedMeshId = 0;
|
||||
if (unionEnabled) {
|
||||
mergedMeshId = unionMeshs(meshliteContext, meshIds);
|
||||
} else {
|
||||
mergedMeshId = mergeMeshs(meshliteContext, meshIds);
|
||||
}
|
||||
if (combineEnabled) {
|
||||
if (mergedMeshId > 0) {
|
||||
mergedMeshId = meshlite_combine_coplanar_faces(meshliteContext, mergedMeshId);
|
||||
}
|
||||
}
|
||||
if (subdivEnabled) {
|
||||
if (mergedMeshId > 0 && meshlite_is_triangulated_manifold(meshliteContext, mergedMeshId)) {
|
||||
mergedMeshId = meshlite_subdivide(meshliteContext, mergedMeshId);
|
||||
}
|
||||
}
|
||||
if (mergedMeshId > 0) {
|
||||
m_mesh = new Mesh(meshliteContext, mergedMeshId);
|
||||
}
|
||||
}
|
||||
|
||||
meshlite_destroy_context(meshliteContext);
|
||||
emit finished();
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
#ifndef SKELETON_TO_MESH_H
|
||||
#define SKELETON_TO_MESH_H
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "skeletoneditgraphicsview.h"
|
||||
#include "mesh.h"
|
||||
#include "skeletonsnapshot.h"
|
||||
|
||||
class SkeletonToMesh : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SkeletonToMesh(SkeletonSnapshot *snapshot);
|
||||
~SkeletonToMesh();
|
||||
Mesh *takeResultMesh();
|
||||
signals:
|
||||
void finished();
|
||||
public slots:
|
||||
void process();
|
||||
private:
|
||||
Mesh *m_mesh;
|
||||
SkeletonSnapshot m_snapshot;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,214 +0,0 @@
|
|||
#include <QVBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QButtonGroup>
|
||||
#include <QGridLayout>
|
||||
#include <QToolBar>
|
||||
#include <QThread>
|
||||
#include <QFileDialog>
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
#include <assert.h>
|
||||
#include "skeletonwidget.h"
|
||||
#include "meshlite.h"
|
||||
#include "skeletontomesh.h"
|
||||
#include "turnaroundloader.h"
|
||||
|
||||
SkeletonWidget::SkeletonWidget(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
m_skeletonToMesh(NULL),
|
||||
m_skeletonDirty(false),
|
||||
m_turnaroundLoader(NULL),
|
||||
m_turnaroundDirty(false)
|
||||
{
|
||||
QHBoxLayout *topLayout = new QHBoxLayout;
|
||||
topLayout->addStretch();
|
||||
topLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_graphicsView = new SkeletonEditGraphicsView(this);
|
||||
m_graphicsView->setRenderHint(QPainter::Antialiasing, false);
|
||||
m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_graphicsView->setBackgroundBrush(QBrush(QWidget::palette().color(QWidget::backgroundRole()), Qt::SolidPattern));
|
||||
m_graphicsView->setContentsMargins(0, 0, 0, 0);
|
||||
m_graphicsView->setFrameStyle(QFrame::NoFrame);
|
||||
|
||||
/*
|
||||
m_modelWidget = new ModelWidget(this);
|
||||
m_modelWidget->setMinimumSize(128, 128);
|
||||
m_modelWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
m_modelWidget->setWindowFlags(Qt::Tool | Qt::Window);
|
||||
m_modelWidget->setWindowTitle("3D Model");
|
||||
*/
|
||||
|
||||
QVBoxLayout *rightLayout = new QVBoxLayout;
|
||||
rightLayout->addSpacing(0);
|
||||
rightLayout->addStretch();
|
||||
rightLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
QToolBar *toolbar = new QToolBar;
|
||||
toolbar->setIconSize(QSize(18, 18));
|
||||
toolbar->setOrientation(Qt::Vertical);
|
||||
|
||||
QAction *addAction = new QAction(tr("Add"), this);
|
||||
addAction->setIcon(QPixmap(":/resources/add.svg"));
|
||||
toolbar->addAction(addAction);
|
||||
|
||||
QAction *selectAction = new QAction(tr("Select"), this);
|
||||
selectAction->setIcon(QPixmap(":/resources/rotate.svg"));
|
||||
toolbar->addAction(selectAction);
|
||||
|
||||
QAction *rangeSelectAction = new QAction(tr("Range Select"), this);
|
||||
rangeSelectAction->setIcon(QPixmap(":/resources/zoomin.svg"));
|
||||
toolbar->addAction(rangeSelectAction);
|
||||
|
||||
toolbar->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
QVBoxLayout *leftLayout = new QVBoxLayout;
|
||||
leftLayout->addWidget(toolbar);
|
||||
leftLayout->addStretch();
|
||||
leftLayout->addSpacing(0);
|
||||
leftLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
QHBoxLayout *middleLayout = new QHBoxLayout;
|
||||
//middleLayout->addLayout(leftLayout);
|
||||
middleLayout->addWidget(m_graphicsView);
|
||||
//middleLayout->addLayout(rightLayout);
|
||||
middleLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
//mainLayout->addLayout(topLayout);
|
||||
//mainLayout->addSpacing(10);
|
||||
mainLayout->addLayout(middleLayout);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
setLayout(mainLayout);
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
setWindowTitle(tr("Dust 3D"));
|
||||
|
||||
bool connectResult;
|
||||
|
||||
connectResult = connect(addAction, SIGNAL(triggered(bool)), m_graphicsView, SLOT(turnOnAddNodeMode()));
|
||||
assert(connectResult);
|
||||
|
||||
connectResult = connectResult = connect(selectAction, SIGNAL(triggered(bool)), m_graphicsView, SLOT(turnOffAddNodeMode()));
|
||||
assert(connectResult);
|
||||
|
||||
connectResult = connect(m_graphicsView, SIGNAL(nodesChanged()), this, SLOT(skeletonChanged()));
|
||||
assert(connectResult);
|
||||
|
||||
connectResult = connect(m_graphicsView, SIGNAL(sizeChanged()), this, SLOT(turnaroundChanged()));
|
||||
assert(connectResult);
|
||||
|
||||
connectResult = connect(m_graphicsView, SIGNAL(changeTurnaroundTriggered()), this, SLOT(changeTurnaround()));
|
||||
assert(connectResult);
|
||||
|
||||
//connectResult = connect(clipButton, SIGNAL(clicked()), this, SLOT(saveClip()));
|
||||
//assert(connectResult);
|
||||
}
|
||||
|
||||
SkeletonEditGraphicsView *SkeletonWidget::graphicsView()
|
||||
{
|
||||
return m_graphicsView;
|
||||
}
|
||||
|
||||
ModelWidget *SkeletonWidget::modelWidget()
|
||||
{
|
||||
//return m_modelWidget;
|
||||
return graphicsView()->modelWidget();
|
||||
}
|
||||
|
||||
void SkeletonWidget::showModelingWidgetAtCorner()
|
||||
{
|
||||
/*
|
||||
if (!m_modelWidget->isVisible()) {
|
||||
QPoint pos = QPoint(QApplication::desktop()->width(),
|
||||
QApplication::desktop()->height());
|
||||
m_modelWidget->move(pos.x() - m_modelWidget->width(),
|
||||
pos.y() - m_modelWidget->height());
|
||||
m_modelWidget->show();
|
||||
}*/
|
||||
}
|
||||
|
||||
void SkeletonWidget::meshReady()
|
||||
{
|
||||
Mesh *resultMesh = m_skeletonToMesh->takeResultMesh();
|
||||
//showModelingWidgetAtCorner();
|
||||
//m_modelWidget->updateMesh(resultMesh);
|
||||
modelWidget()->updateMesh(resultMesh);
|
||||
delete m_skeletonToMesh;
|
||||
m_skeletonToMesh = NULL;
|
||||
if (m_skeletonDirty) {
|
||||
skeletonChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonWidget::skeletonChanged()
|
||||
{
|
||||
if (m_skeletonToMesh) {
|
||||
m_skeletonDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
m_skeletonDirty = false;
|
||||
|
||||
QThread *thread = new QThread;
|
||||
SkeletonSnapshot snapshot;
|
||||
m_graphicsView->saveToSnapshot(&snapshot);
|
||||
m_skeletonToMesh = new SkeletonToMesh(&snapshot);
|
||||
m_skeletonToMesh->moveToThread(thread);
|
||||
connect(thread, SIGNAL(started()), m_skeletonToMesh, SLOT(process()));
|
||||
connect(m_skeletonToMesh, SIGNAL(finished()), this, SLOT(meshReady()));
|
||||
connect(m_skeletonToMesh, SIGNAL(finished()), thread, SLOT(quit()));
|
||||
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void SkeletonWidget::turnaroundChanged()
|
||||
{
|
||||
if (m_turnaroundFilename.isEmpty())
|
||||
return;
|
||||
|
||||
if (m_turnaroundLoader) {
|
||||
m_turnaroundDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
m_turnaroundDirty = false;
|
||||
|
||||
QThread *thread = new QThread;
|
||||
m_turnaroundLoader = new TurnaroundLoader(m_turnaroundFilename,
|
||||
m_graphicsView->rect().size());
|
||||
m_turnaroundLoader->moveToThread(thread);
|
||||
connect(thread, SIGNAL(started()), m_turnaroundLoader, SLOT(process()));
|
||||
connect(m_turnaroundLoader, SIGNAL(finished()), this, SLOT(turnaroundImageReady()));
|
||||
connect(m_turnaroundLoader, SIGNAL(finished()), thread, SLOT(quit()));
|
||||
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void SkeletonWidget::turnaroundImageReady()
|
||||
{
|
||||
QImage *backgroundImage = m_turnaroundLoader->takeResultImage();
|
||||
if (backgroundImage && backgroundImage->width() > 0 && backgroundImage->height() > 0) {
|
||||
m_graphicsView->updateBackgroundImage(*backgroundImage);
|
||||
}
|
||||
delete backgroundImage;
|
||||
delete m_turnaroundLoader;
|
||||
m_turnaroundLoader = NULL;
|
||||
if (m_turnaroundDirty) {
|
||||
turnaroundChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonWidget::changeTurnaround()
|
||||
{
|
||||
QString fileName = QFileDialog::getOpenFileName(this, tr("Open Turnaround Reference Image"),
|
||||
QString(),
|
||||
tr("Image Files (*.png *.jpg *.bmp)")).trimmed();
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
m_turnaroundFilename = fileName;
|
||||
turnaroundChanged();
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
#ifndef SKELETON_WIDGET_H
|
||||
#define SKELETON_WIDGET_H
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include <QVBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include "modelwidget.h"
|
||||
#include "skeletontomesh.h"
|
||||
#include "turnaroundloader.h"
|
||||
#include "skeletoneditgraphicsview.h"
|
||||
|
||||
class SkeletonWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SkeletonWidget(QWidget *parent=0);
|
||||
SkeletonEditGraphicsView *graphicsView();
|
||||
ModelWidget *modelWidget();
|
||||
public slots:
|
||||
void skeletonChanged();
|
||||
void meshReady();
|
||||
void turnaroundChanged();
|
||||
void turnaroundImageReady();
|
||||
void changeTurnaround();
|
||||
void showModelingWidgetAtCorner();
|
||||
private:
|
||||
ModelWidget *m_modelWidget;
|
||||
SkeletonEditGraphicsView *m_graphicsView;
|
||||
SkeletonToMesh *m_skeletonToMesh;
|
||||
bool m_skeletonDirty;
|
||||
TurnaroundLoader *m_turnaroundLoader;
|
||||
bool m_turnaroundDirty;
|
||||
QString m_turnaroundFilename;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -25,6 +25,23 @@ float Theme::edgeAlpha = 1.0;
|
|||
float Theme::fillAlpha = 50.0 / 255;
|
||||
int Theme::skeletonNodeBorderSize = 0;
|
||||
int Theme::skeletonEdgeWidth = 0;
|
||||
int Theme::toolIconFontSize = 16;
|
||||
int Theme::toolIconSize = 24;
|
||||
int Theme::previewImageSize = 64;
|
||||
|
||||
QtAwesome *Theme::awesome()
|
||||
{
|
||||
static QtAwesome *s_awesome = nullptr;
|
||||
if (nullptr == s_awesome) {
|
||||
s_awesome = new QtAwesome();
|
||||
s_awesome->initFontAwesome();
|
||||
s_awesome->setDefaultOption("color", Theme::white);
|
||||
s_awesome->setDefaultOption("color-disabled", QColor(0xcc, 0xcc, 0xcc));
|
||||
s_awesome->setDefaultOption("color-active", Theme::white);
|
||||
s_awesome->setDefaultOption("color-selected", Theme::white);
|
||||
}
|
||||
return s_awesome;
|
||||
}
|
||||
|
||||
std::map<QString, QString> createSideColorNameMap() {
|
||||
std::map<QString, QString> map;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <QColor>
|
||||
#include <QString>
|
||||
#include <map>
|
||||
#include "QtAwesome.h"
|
||||
|
||||
class Theme
|
||||
{
|
||||
|
@ -22,6 +23,10 @@ public:
|
|||
static QString tabButtonStylesheet;
|
||||
static std::map<QString, QString> nextSideColorNameMap;
|
||||
static std::map<QString, QColor> sideColorNameToColorMap;
|
||||
static QtAwesome *awesome();
|
||||
static int toolIconFontSize;
|
||||
static int toolIconSize;
|
||||
static int previewImageSize;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
#include "turnaroundloader.h"
|
||||
|
||||
TurnaroundLoader::TurnaroundLoader(const QString &filename, QSize viewSize) :
|
||||
m_resultImage(NULL)
|
||||
m_resultImage(nullptr)
|
||||
{
|
||||
m_filename = filename;
|
||||
m_viewSize = viewSize;
|
||||
}
|
||||
|
||||
TurnaroundLoader::TurnaroundLoader(const QImage &image, QSize viewSize)
|
||||
{
|
||||
m_inputImage = image;
|
||||
m_viewSize = viewSize;
|
||||
}
|
||||
|
||||
TurnaroundLoader::~TurnaroundLoader()
|
||||
{
|
||||
delete m_resultImage;
|
||||
|
@ -15,13 +21,17 @@ TurnaroundLoader::~TurnaroundLoader()
|
|||
QImage *TurnaroundLoader::takeResultImage()
|
||||
{
|
||||
QImage *returnImage = m_resultImage;
|
||||
m_resultImage = NULL;
|
||||
m_resultImage = nullptr;
|
||||
return returnImage;
|
||||
}
|
||||
|
||||
void TurnaroundLoader::process()
|
||||
{
|
||||
QImage image(m_filename);
|
||||
m_resultImage = new QImage(image.scaled(m_viewSize, Qt::KeepAspectRatio));
|
||||
if (m_inputImage.isNull()) {
|
||||
QImage image(m_filename);
|
||||
m_resultImage = new QImage(image.scaled(m_viewSize, Qt::KeepAspectRatio));
|
||||
} else {
|
||||
m_resultImage = new QImage(m_inputImage.scaled(m_viewSize, Qt::KeepAspectRatio));
|
||||
}
|
||||
emit finished();
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ class TurnaroundLoader : public QObject
|
|||
Q_OBJECT
|
||||
public:
|
||||
TurnaroundLoader(const QString &filename, QSize viewSize);
|
||||
TurnaroundLoader(const QImage &image, QSize viewSize);
|
||||
~TurnaroundLoader();
|
||||
QImage *takeResultImage();
|
||||
signals:
|
||||
|
@ -18,6 +19,7 @@ public slots:
|
|||
void process();
|
||||
private:
|
||||
QImage *m_resultImage;
|
||||
QImage m_inputImage;
|
||||
QString m_filename;
|
||||
QSize m_viewSize;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
#include <cmath>
|
||||
#include "util.h"
|
||||
|
||||
QString valueOfKeyInMapOrEmpty(const std::map<QString, QString> &map, const QString &key)
|
||||
{
|
||||
auto it = map.find(key);
|
||||
if (it == map.end())
|
||||
return QString();
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool isTrueValueString(const QString &str)
|
||||
{
|
||||
return "true" == str || "True" == str || "1" == str;
|
||||
}
|
||||
|
||||
bool isFloatEqual(float a, float b)
|
||||
{
|
||||
return fabs(a - b) <= 0.000001;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
#include <QString>
|
||||
#include <map>
|
||||
|
||||
QString valueOfKeyInMapOrEmpty(const std::map<QString, QString> &map, const QString &key);
|
||||
bool isTrueValueString(const QString &str);
|
||||
bool isFloatEqual(float a, float b);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,29 @@
|
|||
MIT License
|
||||
===========
|
||||
|
||||
Copyright 2013-2015 [Reliable Bits Software by Blommers IT](http://blommersit.nl). All Rights Reserved.
|
||||
Author [Rick Blommers](mailto:rick@blommersit.nl)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
||||
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
Font Awesome License
|
||||
====================
|
||||
|
||||
[https://github.com/FortAwesome/Font-Awesome](https://github.com/FortAwesome/Font-Awesome)
|
||||
|
||||
The Font Awesome font is licensed under the SIL Open Font License - [http://scripts.sil.org/OFL](http://scripts.sil.org/OFL)
|
||||
The Font Awesome pictograms are licensed under the CC BY 3.0 License - [http://creativecommons.org/licenses/by/3.0/](http://creativecommons.org/licenses/by/3.0/)
|
||||
"Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome"
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,876 @@
|
|||
/**
|
||||
* QtAwesome - use font-awesome (or other font icons) in your c++ / Qt Application
|
||||
*
|
||||
* MIT Licensed
|
||||
*
|
||||
* Copyright 2013-2015 - Reliable Bits Software by Blommers IT. All Rights Reserved.
|
||||
* Author Rick Blommers
|
||||
*/
|
||||
|
||||
#ifndef QTAWESOME_H
|
||||
#define QTAWESOME_H
|
||||
|
||||
#include "QtAwesomeAnim.h"
|
||||
|
||||
#include <QIcon>
|
||||
#include <QIconEngine>
|
||||
#include <QPainter>
|
||||
#include <QRect>
|
||||
#include <QVariantMap>
|
||||
|
||||
|
||||
/// A list of all icon-names with the codepoint (unicode-value) on the right
|
||||
/// You can use the names on the page http://fortawesome.github.io/Font-Awesome/design.html
|
||||
namespace fa {
|
||||
enum icon {
|
||||
fa_500px = 0xf26e,
|
||||
addressbook = 0xf2b9,
|
||||
addressbooko = 0xf2ba,
|
||||
addresscard = 0xf2bb,
|
||||
addresscardo = 0xf2bc,
|
||||
adjust = 0xf042,
|
||||
adn = 0xf170,
|
||||
aligncenter = 0xf037,
|
||||
alignjustify = 0xf039,
|
||||
alignleft = 0xf036,
|
||||
alignright = 0xf038,
|
||||
amazon = 0xf270,
|
||||
ambulance = 0xf0f9,
|
||||
americansignlanguageinterpreting = 0xf2a3,
|
||||
anchor = 0xf13d,
|
||||
android = 0xf17b,
|
||||
angellist = 0xf209,
|
||||
angledoubledown = 0xf103,
|
||||
angledoubleleft = 0xf100,
|
||||
angledoubleright = 0xf101,
|
||||
angledoubleup = 0xf102,
|
||||
angledown = 0xf107,
|
||||
angleleft = 0xf104,
|
||||
angleright = 0xf105,
|
||||
angleup = 0xf106,
|
||||
apple = 0xf179,
|
||||
archive = 0xf187,
|
||||
areachart = 0xf1fe,
|
||||
arrowcircledown = 0xf0ab,
|
||||
arrowcircleleft = 0xf0a8,
|
||||
arrowcircleodown = 0xf01a,
|
||||
arrowcircleoleft = 0xf190,
|
||||
arrowcircleoright = 0xf18e,
|
||||
arrowcircleoup = 0xf01b,
|
||||
arrowcircleright = 0xf0a9,
|
||||
arrowcircleup = 0xf0aa,
|
||||
arrowdown = 0xf063,
|
||||
arrowleft = 0xf060,
|
||||
arrowright = 0xf061,
|
||||
arrowup = 0xf062,
|
||||
arrows = 0xf047,
|
||||
arrowsalt = 0xf0b2,
|
||||
arrowsh = 0xf07e,
|
||||
arrowsv = 0xf07d,
|
||||
aslinterpreting = 0xf2a3,
|
||||
assistivelisteningsystems = 0xf2a2,
|
||||
asterisk = 0xf069,
|
||||
at = 0xf1fa,
|
||||
audiodescription = 0xf29e,
|
||||
automobile = 0xf1b9,
|
||||
backward = 0xf04a,
|
||||
balancescale = 0xf24e,
|
||||
ban = 0xf05e,
|
||||
bandcamp = 0xf2d5,
|
||||
bank = 0xf19c,
|
||||
barchart = 0xf080,
|
||||
barcharto = 0xf080,
|
||||
barcode = 0xf02a,
|
||||
bars = 0xf0c9,
|
||||
bath = 0xf2cd,
|
||||
bathtub = 0xf2cd,
|
||||
battery = 0xf240,
|
||||
battery0 = 0xf244,
|
||||
battery1 = 0xf243,
|
||||
battery2 = 0xf242,
|
||||
battery3 = 0xf241,
|
||||
battery4 = 0xf240,
|
||||
batteryempty = 0xf244,
|
||||
batteryfull = 0xf240,
|
||||
batteryhalf = 0xf242,
|
||||
batteryquarter = 0xf243,
|
||||
batterythreequarters = 0xf241,
|
||||
bed = 0xf236,
|
||||
beer = 0xf0fc,
|
||||
behance = 0xf1b4,
|
||||
behancesquare = 0xf1b5,
|
||||
bell = 0xf0f3,
|
||||
bello = 0xf0a2,
|
||||
bellslash = 0xf1f6,
|
||||
bellslasho = 0xf1f7,
|
||||
bicycle = 0xf206,
|
||||
binoculars = 0xf1e5,
|
||||
birthdaycake = 0xf1fd,
|
||||
bitbucket = 0xf171,
|
||||
bitbucketsquare = 0xf172,
|
||||
bitcoin = 0xf15a,
|
||||
blacktie = 0xf27e,
|
||||
blind = 0xf29d,
|
||||
bluetooth = 0xf293,
|
||||
bluetoothb = 0xf294,
|
||||
bold = 0xf032,
|
||||
bolt = 0xf0e7,
|
||||
bomb = 0xf1e2,
|
||||
book = 0xf02d,
|
||||
bookmark = 0xf02e,
|
||||
bookmarko = 0xf097,
|
||||
braille = 0xf2a1,
|
||||
briefcase = 0xf0b1,
|
||||
btc = 0xf15a,
|
||||
bug = 0xf188,
|
||||
building = 0xf1ad,
|
||||
buildingo = 0xf0f7,
|
||||
bullhorn = 0xf0a1,
|
||||
bullseye = 0xf140,
|
||||
bus = 0xf207,
|
||||
buysellads = 0xf20d,
|
||||
cab = 0xf1ba,
|
||||
calculator = 0xf1ec,
|
||||
calendar = 0xf073,
|
||||
calendarchecko = 0xf274,
|
||||
calendarminuso = 0xf272,
|
||||
calendaro = 0xf133,
|
||||
calendarpluso = 0xf271,
|
||||
calendartimeso = 0xf273,
|
||||
camera = 0xf030,
|
||||
cameraretro = 0xf083,
|
||||
car = 0xf1b9,
|
||||
caretdown = 0xf0d7,
|
||||
caretleft = 0xf0d9,
|
||||
caretright = 0xf0da,
|
||||
caretsquareodown = 0xf150,
|
||||
caretsquareoleft = 0xf191,
|
||||
caretsquareoright = 0xf152,
|
||||
caretsquareoup = 0xf151,
|
||||
caretup = 0xf0d8,
|
||||
cartarrowdown = 0xf218,
|
||||
cartplus = 0xf217,
|
||||
cc = 0xf20a,
|
||||
ccamex = 0xf1f3,
|
||||
ccdinersclub = 0xf24c,
|
||||
ccdiscover = 0xf1f2,
|
||||
ccjcb = 0xf24b,
|
||||
ccmastercard = 0xf1f1,
|
||||
ccpaypal = 0xf1f4,
|
||||
ccstripe = 0xf1f5,
|
||||
ccvisa = 0xf1f0,
|
||||
certificate = 0xf0a3,
|
||||
chain = 0xf0c1,
|
||||
chainbroken = 0xf127,
|
||||
check = 0xf00c,
|
||||
checkcircle = 0xf058,
|
||||
checkcircleo = 0xf05d,
|
||||
checksquare = 0xf14a,
|
||||
checksquareo = 0xf046,
|
||||
chevroncircledown = 0xf13a,
|
||||
chevroncircleleft = 0xf137,
|
||||
chevroncircleright = 0xf138,
|
||||
chevroncircleup = 0xf139,
|
||||
chevrondown = 0xf078,
|
||||
chevronleft = 0xf053,
|
||||
chevronright = 0xf054,
|
||||
chevronup = 0xf077,
|
||||
child = 0xf1ae,
|
||||
chrome = 0xf268,
|
||||
circle = 0xf111,
|
||||
circleo = 0xf10c,
|
||||
circleonotch = 0xf1ce,
|
||||
circlethin = 0xf1db,
|
||||
clipboard = 0xf0ea,
|
||||
clocko = 0xf017,
|
||||
clone = 0xf24d,
|
||||
close = 0xf00d,
|
||||
cloud = 0xf0c2,
|
||||
clouddownload = 0xf0ed,
|
||||
cloudupload = 0xf0ee,
|
||||
cny = 0xf157,
|
||||
code = 0xf121,
|
||||
codefork = 0xf126,
|
||||
codepen = 0xf1cb,
|
||||
codiepie = 0xf284,
|
||||
coffee = 0xf0f4,
|
||||
cog = 0xf013,
|
||||
cogs = 0xf085,
|
||||
columns = 0xf0db,
|
||||
comment = 0xf075,
|
||||
commento = 0xf0e5,
|
||||
commenting = 0xf27a,
|
||||
commentingo = 0xf27b,
|
||||
comments = 0xf086,
|
||||
commentso = 0xf0e6,
|
||||
compass = 0xf14e,
|
||||
compress = 0xf066,
|
||||
connectdevelop = 0xf20e,
|
||||
contao = 0xf26d,
|
||||
copy = 0xf0c5,
|
||||
copyright = 0xf1f9,
|
||||
creativecommons = 0xf25e,
|
||||
creditcard = 0xf09d,
|
||||
creditcardalt = 0xf283,
|
||||
crop = 0xf125,
|
||||
crosshairs = 0xf05b,
|
||||
css3 = 0xf13c,
|
||||
cube = 0xf1b2,
|
||||
cubes = 0xf1b3,
|
||||
cut = 0xf0c4,
|
||||
cutlery = 0xf0f5,
|
||||
dashboard = 0xf0e4,
|
||||
dashcube = 0xf210,
|
||||
database = 0xf1c0,
|
||||
deaf = 0xf2a4,
|
||||
deafness = 0xf2a4,
|
||||
dedent = 0xf03b,
|
||||
delicious = 0xf1a5,
|
||||
desktop = 0xf108,
|
||||
deviantart = 0xf1bd,
|
||||
diamond = 0xf219,
|
||||
digg = 0xf1a6,
|
||||
dollar = 0xf155,
|
||||
dotcircleo = 0xf192,
|
||||
download = 0xf019,
|
||||
dribbble = 0xf17d,
|
||||
driverslicense = 0xf2c2,
|
||||
driverslicenseo = 0xf2c3,
|
||||
dropbox = 0xf16b,
|
||||
drupal = 0xf1a9,
|
||||
edge = 0xf282,
|
||||
edit = 0xf044,
|
||||
eercast = 0xf2da,
|
||||
eject = 0xf052,
|
||||
ellipsish = 0xf141,
|
||||
ellipsisv = 0xf142,
|
||||
empire = 0xf1d1,
|
||||
envelope = 0xf0e0,
|
||||
envelopeo = 0xf003,
|
||||
envelopeopen = 0xf2b6,
|
||||
envelopeopeno = 0xf2b7,
|
||||
envelopesquare = 0xf199,
|
||||
envira = 0xf299,
|
||||
eraser = 0xf12d,
|
||||
etsy = 0xf2d7,
|
||||
eur = 0xf153,
|
||||
euro = 0xf153,
|
||||
exchange = 0xf0ec,
|
||||
exclamation = 0xf12a,
|
||||
exclamationcircle = 0xf06a,
|
||||
exclamationtriangle = 0xf071,
|
||||
expand = 0xf065,
|
||||
expeditedssl = 0xf23e,
|
||||
externallink = 0xf08e,
|
||||
externallinksquare = 0xf14c,
|
||||
eye = 0xf06e,
|
||||
eyeslash = 0xf070,
|
||||
eyedropper = 0xf1fb,
|
||||
fa = 0xf2b4,
|
||||
facebook = 0xf09a,
|
||||
facebookf = 0xf09a,
|
||||
facebookofficial = 0xf230,
|
||||
facebooksquare = 0xf082,
|
||||
fastbackward = 0xf049,
|
||||
fastforward = 0xf050,
|
||||
fax = 0xf1ac,
|
||||
feed = 0xf09e,
|
||||
female = 0xf182,
|
||||
fighterjet = 0xf0fb,
|
||||
file = 0xf15b,
|
||||
filearchiveo = 0xf1c6,
|
||||
fileaudioo = 0xf1c7,
|
||||
filecodeo = 0xf1c9,
|
||||
fileexcelo = 0xf1c3,
|
||||
fileimageo = 0xf1c5,
|
||||
filemovieo = 0xf1c8,
|
||||
fileo = 0xf016,
|
||||
filepdfo = 0xf1c1,
|
||||
filephotoo = 0xf1c5,
|
||||
filepictureo = 0xf1c5,
|
||||
filepowerpointo = 0xf1c4,
|
||||
filesoundo = 0xf1c7,
|
||||
filetext = 0xf15c,
|
||||
filetexto = 0xf0f6,
|
||||
filevideoo = 0xf1c8,
|
||||
filewordo = 0xf1c2,
|
||||
filezipo = 0xf1c6,
|
||||
fileso = 0xf0c5,
|
||||
film = 0xf008,
|
||||
filter = 0xf0b0,
|
||||
fire = 0xf06d,
|
||||
fireextinguisher = 0xf134,
|
||||
firefox = 0xf269,
|
||||
firstorder = 0xf2b0,
|
||||
flag = 0xf024,
|
||||
flagcheckered = 0xf11e,
|
||||
flago = 0xf11d,
|
||||
flash = 0xf0e7,
|
||||
flask = 0xf0c3,
|
||||
flickr = 0xf16e,
|
||||
floppyo = 0xf0c7,
|
||||
folder = 0xf07b,
|
||||
foldero = 0xf114,
|
||||
folderopen = 0xf07c,
|
||||
folderopeno = 0xf115,
|
||||
font = 0xf031,
|
||||
fontawesome = 0xf2b4,
|
||||
fonticons = 0xf280,
|
||||
fortawesome = 0xf286,
|
||||
forumbee = 0xf211,
|
||||
forward = 0xf04e,
|
||||
foursquare = 0xf180,
|
||||
freecodecamp = 0xf2c5,
|
||||
frowno = 0xf119,
|
||||
futbolo = 0xf1e3,
|
||||
gamepad = 0xf11b,
|
||||
gavel = 0xf0e3,
|
||||
gbp = 0xf154,
|
||||
ge = 0xf1d1,
|
||||
gear = 0xf013,
|
||||
gears = 0xf085,
|
||||
genderless = 0xf22d,
|
||||
getpocket = 0xf265,
|
||||
gg = 0xf260,
|
||||
ggcircle = 0xf261,
|
||||
gift = 0xf06b,
|
||||
git = 0xf1d3,
|
||||
gitsquare = 0xf1d2,
|
||||
github = 0xf09b,
|
||||
githubalt = 0xf113,
|
||||
githubsquare = 0xf092,
|
||||
gitlab = 0xf296,
|
||||
gittip = 0xf184,
|
||||
glass = 0xf000,
|
||||
glide = 0xf2a5,
|
||||
glideg = 0xf2a6,
|
||||
globe = 0xf0ac,
|
||||
google = 0xf1a0,
|
||||
googleplus = 0xf0d5,
|
||||
googlepluscircle = 0xf2b3,
|
||||
googleplusofficial = 0xf2b3,
|
||||
googleplussquare = 0xf0d4,
|
||||
googlewallet = 0xf1ee,
|
||||
graduationcap = 0xf19d,
|
||||
gratipay = 0xf184,
|
||||
grav = 0xf2d6,
|
||||
group = 0xf0c0,
|
||||
hsquare = 0xf0fd,
|
||||
hackernews = 0xf1d4,
|
||||
handgrabo = 0xf255,
|
||||
handlizardo = 0xf258,
|
||||
handodown = 0xf0a7,
|
||||
handoleft = 0xf0a5,
|
||||
handoright = 0xf0a4,
|
||||
handoup = 0xf0a6,
|
||||
handpapero = 0xf256,
|
||||
handpeaceo = 0xf25b,
|
||||
handpointero = 0xf25a,
|
||||
handrocko = 0xf255,
|
||||
handscissorso = 0xf257,
|
||||
handspocko = 0xf259,
|
||||
handstopo = 0xf256,
|
||||
handshakeo = 0xf2b5,
|
||||
hardofhearing = 0xf2a4,
|
||||
hashtag = 0xf292,
|
||||
hddo = 0xf0a0,
|
||||
header = 0xf1dc,
|
||||
headphones = 0xf025,
|
||||
heart = 0xf004,
|
||||
hearto = 0xf08a,
|
||||
heartbeat = 0xf21e,
|
||||
history = 0xf1da,
|
||||
home = 0xf015,
|
||||
hospitalo = 0xf0f8,
|
||||
hotel = 0xf236,
|
||||
hourglass = 0xf254,
|
||||
hourglass1 = 0xf251,
|
||||
hourglass2 = 0xf252,
|
||||
hourglass3 = 0xf253,
|
||||
hourglassend = 0xf253,
|
||||
hourglasshalf = 0xf252,
|
||||
hourglasso = 0xf250,
|
||||
hourglassstart = 0xf251,
|
||||
houzz = 0xf27c,
|
||||
html5 = 0xf13b,
|
||||
icursor = 0xf246,
|
||||
idbadge = 0xf2c1,
|
||||
idcard = 0xf2c2,
|
||||
idcardo = 0xf2c3,
|
||||
ils = 0xf20b,
|
||||
image = 0xf03e,
|
||||
imdb = 0xf2d8,
|
||||
inbox = 0xf01c,
|
||||
indent = 0xf03c,
|
||||
industry = 0xf275,
|
||||
info = 0xf129,
|
||||
infocircle = 0xf05a,
|
||||
inr = 0xf156,
|
||||
instagram = 0xf16d,
|
||||
institution = 0xf19c,
|
||||
internetexplorer = 0xf26b,
|
||||
intersex = 0xf224,
|
||||
ioxhost = 0xf208,
|
||||
italic = 0xf033,
|
||||
joomla = 0xf1aa,
|
||||
jpy = 0xf157,
|
||||
jsfiddle = 0xf1cc,
|
||||
key = 0xf084,
|
||||
keyboardo = 0xf11c,
|
||||
krw = 0xf159,
|
||||
language = 0xf1ab,
|
||||
laptop = 0xf109,
|
||||
lastfm = 0xf202,
|
||||
lastfmsquare = 0xf203,
|
||||
leaf = 0xf06c,
|
||||
leanpub = 0xf212,
|
||||
legal = 0xf0e3,
|
||||
lemono = 0xf094,
|
||||
leveldown = 0xf149,
|
||||
levelup = 0xf148,
|
||||
lifebouy = 0xf1cd,
|
||||
lifebuoy = 0xf1cd,
|
||||
lifering = 0xf1cd,
|
||||
lifesaver = 0xf1cd,
|
||||
lightbulbo = 0xf0eb,
|
||||
linechart = 0xf201,
|
||||
link = 0xf0c1,
|
||||
linkedin = 0xf0e1,
|
||||
linkedinsquare = 0xf08c,
|
||||
linode = 0xf2b8,
|
||||
fa_linux = 0xf17c,
|
||||
list = 0xf03a,
|
||||
listalt = 0xf022,
|
||||
listol = 0xf0cb,
|
||||
listul = 0xf0ca,
|
||||
locationarrow = 0xf124,
|
||||
lock = 0xf023,
|
||||
longarrowdown = 0xf175,
|
||||
longarrowleft = 0xf177,
|
||||
longarrowright = 0xf178,
|
||||
longarrowup = 0xf176,
|
||||
lowvision = 0xf2a8,
|
||||
magic = 0xf0d0,
|
||||
magnet = 0xf076,
|
||||
mailforward = 0xf064,
|
||||
mailreply = 0xf112,
|
||||
mailreplyall = 0xf122,
|
||||
male = 0xf183,
|
||||
map = 0xf279,
|
||||
mapmarker = 0xf041,
|
||||
mapo = 0xf278,
|
||||
mappin = 0xf276,
|
||||
mapsigns = 0xf277,
|
||||
mars = 0xf222,
|
||||
marsdouble = 0xf227,
|
||||
marsstroke = 0xf229,
|
||||
marsstrokeh = 0xf22b,
|
||||
marsstrokev = 0xf22a,
|
||||
maxcdn = 0xf136,
|
||||
meanpath = 0xf20c,
|
||||
medium = 0xf23a,
|
||||
medkit = 0xf0fa,
|
||||
meetup = 0xf2e0,
|
||||
meho = 0xf11a,
|
||||
mercury = 0xf223,
|
||||
microchip = 0xf2db,
|
||||
microphone = 0xf130,
|
||||
microphoneslash = 0xf131,
|
||||
minus = 0xf068,
|
||||
minuscircle = 0xf056,
|
||||
minussquare = 0xf146,
|
||||
minussquareo = 0xf147,
|
||||
mixcloud = 0xf289,
|
||||
mobile = 0xf10b,
|
||||
mobilephone = 0xf10b,
|
||||
modx = 0xf285,
|
||||
money = 0xf0d6,
|
||||
moono = 0xf186,
|
||||
mortarboard = 0xf19d,
|
||||
motorcycle = 0xf21c,
|
||||
mousepointer = 0xf245,
|
||||
music = 0xf001,
|
||||
navicon = 0xf0c9,
|
||||
neuter = 0xf22c,
|
||||
newspapero = 0xf1ea,
|
||||
objectgroup = 0xf247,
|
||||
objectungroup = 0xf248,
|
||||
odnoklassniki = 0xf263,
|
||||
odnoklassnikisquare = 0xf264,
|
||||
opencart = 0xf23d,
|
||||
openid = 0xf19b,
|
||||
opera = 0xf26a,
|
||||
optinmonster = 0xf23c,
|
||||
outdent = 0xf03b,
|
||||
pagelines = 0xf18c,
|
||||
paintbrush = 0xf1fc,
|
||||
paperplane = 0xf1d8,
|
||||
paperplaneo = 0xf1d9,
|
||||
paperclip = 0xf0c6,
|
||||
paragraph = 0xf1dd,
|
||||
paste = 0xf0ea,
|
||||
pause = 0xf04c,
|
||||
pausecircle = 0xf28b,
|
||||
pausecircleo = 0xf28c,
|
||||
paw = 0xf1b0,
|
||||
paypal = 0xf1ed,
|
||||
pencil = 0xf040,
|
||||
pencilsquare = 0xf14b,
|
||||
pencilsquareo = 0xf044,
|
||||
percent = 0xf295,
|
||||
phone = 0xf095,
|
||||
phonesquare = 0xf098,
|
||||
photo = 0xf03e,
|
||||
pictureo = 0xf03e,
|
||||
piechart = 0xf200,
|
||||
piedpiper = 0xf2ae,
|
||||
piedpiperalt = 0xf1a8,
|
||||
piedpiperpp = 0xf1a7,
|
||||
pinterest = 0xf0d2,
|
||||
pinterestp = 0xf231,
|
||||
pinterestsquare = 0xf0d3,
|
||||
plane = 0xf072,
|
||||
play = 0xf04b,
|
||||
playcircle = 0xf144,
|
||||
playcircleo = 0xf01d,
|
||||
plug = 0xf1e6,
|
||||
plus = 0xf067,
|
||||
pluscircle = 0xf055,
|
||||
plussquare = 0xf0fe,
|
||||
plussquareo = 0xf196,
|
||||
podcast = 0xf2ce,
|
||||
poweroff = 0xf011,
|
||||
print = 0xf02f,
|
||||
producthunt = 0xf288,
|
||||
puzzlepiece = 0xf12e,
|
||||
qq = 0xf1d6,
|
||||
qrcode = 0xf029,
|
||||
question = 0xf128,
|
||||
questioncircle = 0xf059,
|
||||
questioncircleo = 0xf29c,
|
||||
quora = 0xf2c4,
|
||||
quoteleft = 0xf10d,
|
||||
quoteright = 0xf10e,
|
||||
ra = 0xf1d0,
|
||||
random = 0xf074,
|
||||
ravelry = 0xf2d9,
|
||||
rebel = 0xf1d0,
|
||||
recycle = 0xf1b8,
|
||||
reddit = 0xf1a1,
|
||||
redditalien = 0xf281,
|
||||
redditsquare = 0xf1a2,
|
||||
refresh = 0xf021,
|
||||
registered = 0xf25d,
|
||||
remove = 0xf00d,
|
||||
renren = 0xf18b,
|
||||
reorder = 0xf0c9,
|
||||
repeat = 0xf01e,
|
||||
reply = 0xf112,
|
||||
replyall = 0xf122,
|
||||
resistance = 0xf1d0,
|
||||
retweet = 0xf079,
|
||||
rmb = 0xf157,
|
||||
road = 0xf018,
|
||||
rocket = 0xf135,
|
||||
rotateleft = 0xf0e2,
|
||||
rotateright = 0xf01e,
|
||||
rouble = 0xf158,
|
||||
rss = 0xf09e,
|
||||
rsssquare = 0xf143,
|
||||
rub = 0xf158,
|
||||
ruble = 0xf158,
|
||||
rupee = 0xf156,
|
||||
s15 = 0xf2cd,
|
||||
safari = 0xf267,
|
||||
save = 0xf0c7,
|
||||
scissors = 0xf0c4,
|
||||
scribd = 0xf28a,
|
||||
search = 0xf002,
|
||||
searchminus = 0xf010,
|
||||
searchplus = 0xf00e,
|
||||
sellsy = 0xf213,
|
||||
send = 0xf1d8,
|
||||
sendo = 0xf1d9,
|
||||
server = 0xf233,
|
||||
share = 0xf064,
|
||||
sharealt = 0xf1e0,
|
||||
sharealtsquare = 0xf1e1,
|
||||
sharesquare = 0xf14d,
|
||||
sharesquareo = 0xf045,
|
||||
shekel = 0xf20b,
|
||||
sheqel = 0xf20b,
|
||||
shield = 0xf132,
|
||||
ship = 0xf21a,
|
||||
shirtsinbulk = 0xf214,
|
||||
shoppingbag = 0xf290,
|
||||
shoppingbasket = 0xf291,
|
||||
shoppingcart = 0xf07a,
|
||||
shower = 0xf2cc,
|
||||
signin = 0xf090,
|
||||
signlanguage = 0xf2a7,
|
||||
signout = 0xf08b,
|
||||
signal = 0xf012,
|
||||
signing = 0xf2a7,
|
||||
simplybuilt = 0xf215,
|
||||
sitemap = 0xf0e8,
|
||||
skyatlas = 0xf216,
|
||||
skype = 0xf17e,
|
||||
slack = 0xf198,
|
||||
sliders = 0xf1de,
|
||||
slideshare = 0xf1e7,
|
||||
smileo = 0xf118,
|
||||
snapchat = 0xf2ab,
|
||||
snapchatghost = 0xf2ac,
|
||||
snapchatsquare = 0xf2ad,
|
||||
snowflakeo = 0xf2dc,
|
||||
soccerballo = 0xf1e3,
|
||||
sort = 0xf0dc,
|
||||
sortalphaasc = 0xf15d,
|
||||
sortalphadesc = 0xf15e,
|
||||
sortamountasc = 0xf160,
|
||||
sortamountdesc = 0xf161,
|
||||
sortasc = 0xf0de,
|
||||
sortdesc = 0xf0dd,
|
||||
sortdown = 0xf0dd,
|
||||
sortnumericasc = 0xf162,
|
||||
sortnumericdesc = 0xf163,
|
||||
sortup = 0xf0de,
|
||||
soundcloud = 0xf1be,
|
||||
spaceshuttle = 0xf197,
|
||||
spinner = 0xf110,
|
||||
spoon = 0xf1b1,
|
||||
spotify = 0xf1bc,
|
||||
square = 0xf0c8,
|
||||
squareo = 0xf096,
|
||||
stackexchange = 0xf18d,
|
||||
stackoverflow = 0xf16c,
|
||||
star = 0xf005,
|
||||
starhalf = 0xf089,
|
||||
starhalfempty = 0xf123,
|
||||
starhalffull = 0xf123,
|
||||
starhalfo = 0xf123,
|
||||
staro = 0xf006,
|
||||
steam = 0xf1b6,
|
||||
steamsquare = 0xf1b7,
|
||||
stepbackward = 0xf048,
|
||||
stepforward = 0xf051,
|
||||
stethoscope = 0xf0f1,
|
||||
stickynote = 0xf249,
|
||||
stickynoteo = 0xf24a,
|
||||
stop = 0xf04d,
|
||||
stopcircle = 0xf28d,
|
||||
stopcircleo = 0xf28e,
|
||||
streetview = 0xf21d,
|
||||
strikethrough = 0xf0cc,
|
||||
stumbleupon = 0xf1a4,
|
||||
stumbleuponcircle = 0xf1a3,
|
||||
subscript = 0xf12c,
|
||||
subway = 0xf239,
|
||||
suitcase = 0xf0f2,
|
||||
suno = 0xf185,
|
||||
superpowers = 0xf2dd,
|
||||
superscript = 0xf12b,
|
||||
support = 0xf1cd,
|
||||
table = 0xf0ce,
|
||||
tablet = 0xf10a,
|
||||
tachometer = 0xf0e4,
|
||||
tag = 0xf02b,
|
||||
tags = 0xf02c,
|
||||
tasks = 0xf0ae,
|
||||
taxi = 0xf1ba,
|
||||
telegram = 0xf2c6,
|
||||
television = 0xf26c,
|
||||
tencentweibo = 0xf1d5,
|
||||
terminal = 0xf120,
|
||||
textheight = 0xf034,
|
||||
textwidth = 0xf035,
|
||||
th = 0xf00a,
|
||||
thlarge = 0xf009,
|
||||
thlist = 0xf00b,
|
||||
themeisle = 0xf2b2,
|
||||
thermometer = 0xf2c7,
|
||||
thermometer0 = 0xf2cb,
|
||||
thermometer1 = 0xf2ca,
|
||||
thermometer2 = 0xf2c9,
|
||||
thermometer3 = 0xf2c8,
|
||||
thermometer4 = 0xf2c7,
|
||||
thermometerempty = 0xf2cb,
|
||||
thermometerfull = 0xf2c7,
|
||||
thermometerhalf = 0xf2c9,
|
||||
thermometerquarter = 0xf2ca,
|
||||
thermometerthreequarters = 0xf2c8,
|
||||
thumbtack = 0xf08d,
|
||||
thumbsdown = 0xf165,
|
||||
thumbsodown = 0xf088,
|
||||
thumbsoup = 0xf087,
|
||||
thumbsup = 0xf164,
|
||||
ticket = 0xf145,
|
||||
times = 0xf00d,
|
||||
timescircle = 0xf057,
|
||||
timescircleo = 0xf05c,
|
||||
timesrectangle = 0xf2d3,
|
||||
timesrectangleo = 0xf2d4,
|
||||
tint = 0xf043,
|
||||
toggledown = 0xf150,
|
||||
toggleleft = 0xf191,
|
||||
toggleoff = 0xf204,
|
||||
toggleon = 0xf205,
|
||||
toggleright = 0xf152,
|
||||
toggleup = 0xf151,
|
||||
trademark = 0xf25c,
|
||||
train = 0xf238,
|
||||
transgender = 0xf224,
|
||||
transgenderalt = 0xf225,
|
||||
trash = 0xf1f8,
|
||||
trasho = 0xf014,
|
||||
tree = 0xf1bb,
|
||||
trello = 0xf181,
|
||||
tripadvisor = 0xf262,
|
||||
trophy = 0xf091,
|
||||
truck = 0xf0d1,
|
||||
fa_try = 0xf195,
|
||||
tty = 0xf1e4,
|
||||
tumblr = 0xf173,
|
||||
tumblrsquare = 0xf174,
|
||||
turkishlira = 0xf195,
|
||||
tv = 0xf26c,
|
||||
twitch = 0xf1e8,
|
||||
twitter = 0xf099,
|
||||
twittersquare = 0xf081,
|
||||
umbrella = 0xf0e9,
|
||||
underline = 0xf0cd,
|
||||
undo = 0xf0e2,
|
||||
universalaccess = 0xf29a,
|
||||
university = 0xf19c,
|
||||
unlink = 0xf127,
|
||||
unlock = 0xf09c,
|
||||
unlockalt = 0xf13e,
|
||||
unsorted = 0xf0dc,
|
||||
upload = 0xf093,
|
||||
usb = 0xf287,
|
||||
usd = 0xf155,
|
||||
user = 0xf007,
|
||||
usercircle = 0xf2bd,
|
||||
usercircleo = 0xf2be,
|
||||
usermd = 0xf0f0,
|
||||
usero = 0xf2c0,
|
||||
userplus = 0xf234,
|
||||
usersecret = 0xf21b,
|
||||
usertimes = 0xf235,
|
||||
users = 0xf0c0,
|
||||
vcard = 0xf2bb,
|
||||
vcardo = 0xf2bc,
|
||||
venus = 0xf221,
|
||||
venusdouble = 0xf226,
|
||||
venusmars = 0xf228,
|
||||
viacoin = 0xf237,
|
||||
viadeo = 0xf2a9,
|
||||
viadeosquare = 0xf2aa,
|
||||
videocamera = 0xf03d,
|
||||
vimeo = 0xf27d,
|
||||
vimeosquare = 0xf194,
|
||||
vine = 0xf1ca,
|
||||
vk = 0xf189,
|
||||
volumecontrolphone = 0xf2a0,
|
||||
volumedown = 0xf027,
|
||||
volumeoff = 0xf026,
|
||||
volumeup = 0xf028,
|
||||
warning = 0xf071,
|
||||
wechat = 0xf1d7,
|
||||
weibo = 0xf18a,
|
||||
weixin = 0xf1d7,
|
||||
whatsapp = 0xf232,
|
||||
wheelchair = 0xf193,
|
||||
wheelchairalt = 0xf29b,
|
||||
wifi = 0xf1eb,
|
||||
wikipediaw = 0xf266,
|
||||
windowclose = 0xf2d3,
|
||||
windowcloseo = 0xf2d4,
|
||||
windowmaximize = 0xf2d0,
|
||||
windowminimize = 0xf2d1,
|
||||
windowrestore = 0xf2d2,
|
||||
windows = 0xf17a,
|
||||
won = 0xf159,
|
||||
wordpress = 0xf19a,
|
||||
wpbeginner = 0xf297,
|
||||
wpexplorer = 0xf2de,
|
||||
wpforms = 0xf298,
|
||||
wrench = 0xf0ad,
|
||||
xing = 0xf168,
|
||||
xingsquare = 0xf169,
|
||||
ycombinator = 0xf23b,
|
||||
ycombinatorsquare = 0xf1d4,
|
||||
yahoo = 0xf19e,
|
||||
yc = 0xf23b,
|
||||
ycsquare = 0xf1d4,
|
||||
yelp = 0xf1e9,
|
||||
yen = 0xf157,
|
||||
yoast = 0xf2b1,
|
||||
youtube = 0xf167,
|
||||
youtubeplay = 0xf16a,
|
||||
youtubesquare = 0xf166
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------
|
||||
|
||||
class QtAwesomeIconPainter;
|
||||
|
||||
/// The main class for managing icons
|
||||
/// This class requires a 2-phase construction. You must first create the class and then initialize it via an init* method
|
||||
class QtAwesome : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
explicit QtAwesome(QObject *parent = 0);
|
||||
virtual ~QtAwesome();
|
||||
|
||||
void init( const QString& fontname );
|
||||
bool initFontAwesome();
|
||||
|
||||
void addNamedCodepoint( const QString& name, int codePoint );
|
||||
QHash<QString,int> namedCodePoints() { return namedCodepoints_; }
|
||||
|
||||
void setDefaultOption( const QString& name, const QVariant& value );
|
||||
QVariant defaultOption( const QString& name );
|
||||
|
||||
QIcon icon( int character, const QVariantMap& options = QVariantMap() );
|
||||
QIcon icon( const QString& name, const QVariantMap& options = QVariantMap() );
|
||||
QIcon icon(QtAwesomeIconPainter* painter, const QVariantMap& optionMap = QVariantMap() );
|
||||
|
||||
void give( const QString& name, QtAwesomeIconPainter* painter );
|
||||
|
||||
QFont font( int size );
|
||||
|
||||
/// Returns the font-name that is used as icon-map
|
||||
QString fontName() { return fontName_ ; }
|
||||
|
||||
private:
|
||||
QString fontName_; ///< The font name used for this map
|
||||
QHash<QString,int> namedCodepoints_; ///< A map with names mapped to code-points
|
||||
|
||||
QHash<QString, QtAwesomeIconPainter*> painterMap_; ///< A map of custom painters
|
||||
QVariantMap defaultOptions_; ///< The default icon options
|
||||
QtAwesomeIconPainter* fontIconPainter_; ///< A special painter fo painting codepoints
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/// The QtAwesomeIconPainter is a specialized painter for painting icons
|
||||
/// your can implement an iconpainter to create custom font-icon code
|
||||
class QtAwesomeIconPainter
|
||||
{
|
||||
public:
|
||||
virtual ~QtAwesomeIconPainter() {}
|
||||
virtual void paint( QtAwesome* awesome, QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state, const QVariantMap& options ) = 0;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(QtAwesomeAnimation*)
|
||||
|
||||
#endif // QTAWESOME_H
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
SOURCES += $$PWD/QtAwesome.cpp \
|
||||
$$PWD/QtAwesomeAnim.cpp
|
||||
|
||||
HEADERS += $$PWD/QtAwesome.h \
|
||||
$$PWD/QtAwesomeAnim.h
|
||||
|
||||
RESOURCES += $$PWD/QtAwesome.qrc
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
#-------------------------------------------------
|
||||
#
|
||||
# Project created by QtCreator 2013-04-18T13:28:42
|
||||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
TARGET = QtAwesome
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib c++11
|
||||
QT += widgets
|
||||
|
||||
SOURCES += QtAwesome.cpp QtAwesomeAnim.cpp
|
||||
HEADERS += QtAwesome.h QtAwesomeAnim.h
|
||||
|
||||
isEmpty(PREFIX) {
|
||||
unix {
|
||||
PREFIX = /usr
|
||||
} else {
|
||||
PREFIX = $$[QT_INSTALL_PREFIX]
|
||||
}
|
||||
}
|
||||
|
||||
install_headers.files = QtAwesome.h QtAwesomeAnim.h
|
||||
install_headers.path = $$PREFIX/include
|
||||
target.path = $$PREFIX/lib
|
||||
INSTALLS += install_headers target
|
||||
|
||||
RESOURCES += \
|
||||
QtAwesome.qrc
|
|
@ -0,0 +1,5 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>fonts/fontawesome-4.7.0.ttf</file>
|
||||
</qresource>
|
||||
</RCC>
|
|
@ -0,0 +1,46 @@
|
|||
#include "QtAwesomeAnim.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <QPainter>
|
||||
#include <QRect>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
|
||||
QtAwesomeAnimation::QtAwesomeAnimation(QWidget *parentWidget, int interval, int step)
|
||||
: parentWidgetRef_( parentWidget )
|
||||
, timer_( 0 )
|
||||
, interval_( interval )
|
||||
, step_( step )
|
||||
, angle_( 0.0f )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void QtAwesomeAnimation::setup( QPainter &painter, const QRect &rect)
|
||||
{
|
||||
// first time set the timer
|
||||
if( !timer_ )
|
||||
{
|
||||
timer_ = new QTimer();
|
||||
connect(timer_,SIGNAL(timeout()), this, SLOT(update()) );
|
||||
timer_->start(interval_);
|
||||
}
|
||||
else
|
||||
{
|
||||
//timer, angle, self.step = self.info[self.parent_widget]
|
||||
float x_center = rect.width() * 0.5;
|
||||
float y_center = rect.height() * 0.5;
|
||||
painter.translate(x_center, y_center);
|
||||
painter.rotate(angle_);
|
||||
painter.translate(-x_center, -y_center);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void QtAwesomeAnimation::update()
|
||||
{
|
||||
angle_ += step_;
|
||||
angle_ = std::fmod( angle_, 360);
|
||||
parentWidgetRef_->update();
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef QTAWESOMEANIMATION_H
|
||||
#define QTAWESOMEANIMATION_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QPainter;
|
||||
class QRect;
|
||||
class QTimer;
|
||||
class QWidget;
|
||||
|
||||
///
|
||||
/// Basic Animation Support for QtAwesome (Inspired by https://github.com/spyder-ide/qtawesome)
|
||||
///
|
||||
class QtAwesomeAnimation : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QtAwesomeAnimation( QWidget* parentWidget, int interval=10, int step=1);
|
||||
|
||||
void setup( QPainter& painter, const QRect& rect );
|
||||
|
||||
public slots:
|
||||
void update();
|
||||
|
||||
private:
|
||||
QWidget* parentWidgetRef_;
|
||||
QTimer* timer_;
|
||||
int interval_;
|
||||
int step_;
|
||||
float angle_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // QTAWESOMEANIMATION_H
|
Binary file not shown.
|
@ -0,0 +1,20 @@
|
|||
#-------------------------------------------------
|
||||
#
|
||||
# Project created by QtCreator 2013-04-18T15:05:07
|
||||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
QT += core gui
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
TARGET = QtAwesomeSample
|
||||
TEMPLATE = app
|
||||
|
||||
|
||||
SOURCES += main.cpp
|
||||
|
||||
HEADERS +=
|
||||
|
||||
|
||||
include(../QtAwesome/QtAwesome.pri)
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* MIT Licensed
|
||||
*
|
||||
* Copyright 2011-2015 - Reliable Bits Software by Blommers IT. All Rights Reserved.
|
||||
* Author Rick Blommers
|
||||
*/
|
||||
|
||||
#include "QtAwesome.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMainWindow>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
QMainWindow w;
|
||||
|
||||
QtAwesome* awesome = new QtAwesome(&w);
|
||||
awesome->initFontAwesome();
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
|
||||
// a simple beer button
|
||||
//=====================
|
||||
{
|
||||
QPushButton* beerButton = new QPushButton( "Cheers!");
|
||||
|
||||
QVariantMap options;
|
||||
options.insert("anim", qVariantFromValue( new QtAwesomeAnimation(beerButton) ) );
|
||||
beerButton->setIcon( awesome->icon( fa::beer, options ) );
|
||||
|
||||
layout->addWidget(beerButton);
|
||||
}
|
||||
|
||||
// a simple beer checkbox button
|
||||
//==============================
|
||||
{
|
||||
QPushButton* toggleButton = new QPushButton("Toggle Me");
|
||||
toggleButton->setCheckable(true);
|
||||
|
||||
QVariantMap options;
|
||||
options.insert("color", QColor(Qt::green) );
|
||||
options.insert("text-off", QString(fa::squareo) );
|
||||
options.insert("color-off", QColor(Qt::red) );
|
||||
toggleButton->setIcon( awesome->icon( fa::checksquareo, options ));
|
||||
|
||||
|
||||
layout->addWidget(toggleButton);
|
||||
}
|
||||
|
||||
|
||||
// add the samples
|
||||
QWidget* samples = new QWidget();
|
||||
samples->setLayout(layout);
|
||||
w.setCentralWidget(samples);
|
||||
|
||||
w.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
QtAwesome - Font Awesome support for Qt applications
|
||||
====================================================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
QtAwesome is a simple library that can be used to add [Font Awesome](http://fortawesome.github.io/Font-Awesome/) icons to your [Qt application](http://qt-project.org/).
|
||||
|
||||
NOTE: Though the name is QtAwesome and currently it's very Font Awesome based, you can use every other icon/glyph font you want.
|
||||
|
||||
The class can also be used to manage your own dynamic code-drawn icons, by adding named icon-painters.
|
||||
|
||||
|
||||
Updated to FontAwesome 4.7.0
|
||||
----------------------------
|
||||
|
||||
This library has been updated to Font Awesome version **4.7.0**.
|
||||
|
||||
* In the 4.5.0 version the _linux name has been changed to fa_linux. (Makes the naming of conflicting/invalid names more consistent, like fa_try and fa_500px)
|
||||
* You can find the previous FontAwesome 4 c++11 library in the [c++11 branch](https://github.com/gamecreature/QtAwesome/tree/c++11).
|
||||
* You can find the previous FontAwesome 3 library in the [fontawesome-3 branch](https://github.com/gamecreature/QtAwesome/tree/fontawesome-3).
|
||||
|
||||
|
||||
**Note about previous c++11**
|
||||
|
||||
I removed the C++11 requirement. And moved the c++11 code to a c++11 branch.
|
||||
It's not that I don't like c++11, but the typed enum made the code less flexible then it is now.
|
||||
Just integers it is. Simpler is better.
|
||||
|
||||
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The easiest way to include QtAweome in your project is to copy the QtAwesome directory to your
|
||||
project tree and add the following `include()` to your Qt project file:
|
||||
|
||||
include(QtAwesome/QtAwesome.pri)
|
||||
|
||||
Now you are good to go!
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
You probably want to create a single QtAwesome object for your whole application:
|
||||
|
||||
````
|
||||
QtAwesome* awesome = new QtAwesome( qApp )
|
||||
awesome->initFontAwesome(); // This line is important as it loads the font and initializes the named icon map
|
||||
|
||||
````
|
||||
|
||||
* Add an accessor to this object (i.e. a global function, member of your application object, or whatever you like).
|
||||
* Use an icon name from the [Font Awesome Cheatsheet](http://fortawesome.github.io/Font-Awesome/cheatsheet/).
|
||||
|
||||
|
||||
Example
|
||||
--------
|
||||
|
||||
```c++
|
||||
// You should create a single object of QtAwesome.
|
||||
QtAwesome* awesome = new QtAwesome( qApp );
|
||||
awesome->initFontAwesome();
|
||||
|
||||
// Next create your icon with the help of the icon-enumeration (no dashes):
|
||||
QPushButton* beerButton new QPushButton( awesome->icon( fa::beer ), "Cheers!" );
|
||||
|
||||
// You can also use 'string' names to access the icons. (The string version omits the 'fa-' or 'icon-' prefix and has no dashes )
|
||||
QPushButton* coffeeButton new QPushButton( awesome->icon( "coffee" ), "Black please!" );
|
||||
|
||||
// When you create an icon you can supply some options for your icons:
|
||||
// The available options can be found at the "Default options"-section
|
||||
|
||||
QVariantMap options;
|
||||
options.insert( "color" , QColor(255,0,0) );
|
||||
QPushButton* musicButton = new QPushButton( awesome->icon( fa::music, options ), "Music" );
|
||||
|
||||
// You can also change the default options.
|
||||
// for example if you always would like to have green icons you could call)
|
||||
awesome->setDefaultOption( "color-disabled", QColor(0,255,0) );
|
||||
|
||||
// You can also directly render a label with this font
|
||||
QLabel* label = new QLabel( QChar( fa::group ) );
|
||||
label->setFont( awesome->font(16) );
|
||||
|
||||
```
|
||||
|
||||
Example custom painter
|
||||
----------------------
|
||||
|
||||
This example registers a custom painter for supporting a duplicate icon (it draws 2 "plus marks"):
|
||||
|
||||
```c++
|
||||
class DuplicateIconPainter : public QtAwesomeIconPainter
|
||||
{
|
||||
public:
|
||||
virtual void paint( QtAwesome* awesome, QPainter* painter, const QRect& rectIn, QIcon::Mode mode, QIcon::State state, const QVariantMap& options )
|
||||
{
|
||||
int drawSize = qRound(rectIn.height()*0.5);
|
||||
int offset = rectIn.height() / 4;
|
||||
QChar chr = QChar( static_cast<int>(fa::plus) );
|
||||
|
||||
painter->setFont( awesome->font( drawSize ) );
|
||||
|
||||
painter->setPen( QColor(100,100,100) );
|
||||
painter->drawText( QRect( QPoint(offset*2, offset*2), QSize(drawSize, drawSize) ), chr , QTextOption( Qt::AlignCenter|Qt::AlignVCenter ) );
|
||||
|
||||
painter->setPen( QColor(50,50,50) );
|
||||
painter->drawText( QRect( QPoint(rectIn.width()-drawSize-offset, rectIn.height()-drawSize-offset), QSize(drawSize, drawSize) ), chr , QTextOption( Qt::AlignCenter|Qt::AlignVCenter ) );
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
awesome->give("duplicate", new DuplicateIconPainter() );
|
||||
```
|
||||
|
||||
|
||||
Default options:
|
||||
----------------
|
||||
|
||||
The following options are default in the QtAwesome class.
|
||||
|
||||
```c++
|
||||
setDefaultOption( "color", QColor(50,50,50) );
|
||||
setDefaultOption( "color-disabled", QColor(70,70,70,60));
|
||||
setDefaultOption( "color-active", QColor(10,10,10));
|
||||
setDefaultOption( "color-selected", QColor(10,10,10));
|
||||
|
||||
setDefaultOption( "text", QString() ); // internal option
|
||||
setDefaultOption( "text-disabled", QString() );
|
||||
setDefaultOption( "text-active", QString() );
|
||||
setDefaultOption( "text-selected", QString() );
|
||||
|
||||
setDefaultOption( "scale-factor", 0.9 );
|
||||
```
|
||||
|
||||
When creating an icon, it first populates the options-map with the default options from the QtAwesome object.
|
||||
After that the options are expanded/overwritten by the options supplied to the icon.
|
||||
|
||||
It is possible to use another glyph per icon-state. For example to make an icon-unlock symbol switch to locked when selected,
|
||||
you could supply the following option:
|
||||
|
||||
```c++
|
||||
options.insert("text-selected", QString( fa::lock ) );
|
||||
```
|
||||
|
||||
Color and text options have the following structure:
|
||||
`keyname-iconmode-iconstate`
|
||||
|
||||
Where iconmode normal is empty
|
||||
And iconstate On is off.
|
||||
|
||||
So the list of items used is:
|
||||
|
||||
- color
|
||||
- color-disabled
|
||||
- color-active
|
||||
- color-selected
|
||||
- color-off
|
||||
- color-disabled-off
|
||||
- color-active-off
|
||||
- color-selected-off
|
||||
- text
|
||||
- text-disabled
|
||||
- text-active
|
||||
- text-selected
|
||||
- text-off
|
||||
- text-disabled-off
|
||||
- text-active-off
|
||||
- text-selected-off
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
MIT License. Copyright 2013 - Reliable Bits Software by Blommers IT. [http://blommersit.nl/](http://blommersit.nl)
|
||||
|
||||
The Font Awesome font is licensed under the SIL Open Font License - [http://scripts.sil.org/OFL](http://scripts.sil.org/OFL)
|
||||
The Font Awesome pictograms are licensed under the CC BY 3.0 License - [http://creativecommons.org/licenses/by/3.0/](http://creativecommons.org/licenses/by/3.0/)
|
||||
"Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome"
|
||||
|
||||
Contact
|
||||
-------
|
||||
|
||||
* email: <rick@blommersit.nl>
|
||||
* twitter: [https://twitter.com/gamecreature](https://twitter.com/gamecreature)
|
||||
* website: [http://blommersit.nl](http://blommersit.nl) (warning Dutch content ahead)
|
||||
* github: [https://github.com/gamecreature/QtAwesome](https://github.com/gamecreature/QtAwesome)
|
||||
|
||||
|
||||
Known issues and workarounds
|
||||
----------------------------
|
||||
|
||||
On Mac OS X, placing an qtAwesome icon in QMainWindow menu, doesn't work directly.
|
||||
See the following issue: [https://github.com/gamecreature/QtAwesome/issues/10]
|
||||
|
||||
A workaround for this problem is converting it to a Pixmap icon like this:
|
||||
|
||||
```c++
|
||||
QAction* menuAction = new QAction("test");
|
||||
menuAction->setIcon( awesome->icon(fa::beer).pixmap(32,32) );
|
||||
```
|
||||
|
||||
|
||||
Remarks
|
||||
-------
|
||||
|
||||
I've created this project because I needed some nice icons for my own Qt project. After doing a lot of
|
||||
css/html5 work and being spoiled by the ease of twitter bootstrap with Font Awesome,
|
||||
I thought it would be nice to be able to use these icons for my Qt project.
|
||||
|
||||
I've slightly changed the code from the original, added some more documentation, but it's still
|
||||
a work in progress. So feel free to drop me an e-mail for your suggestions and improvements!
|
||||
|
||||
There are still some things todo, like:
|
||||
|
||||
* document the usage of another icon font
|
||||
* add some tests
|
||||
* do some code cleanup
|
||||
|
||||
Thanks go to the contributors of this project!
|
||||
|
||||
And of course last but not least,
|
||||
|
||||
Many thanks go to Dave Gandy an the other Font Awesome contributors!! [http://fortawesome.github.com/Font-Awesome](http://fortawesome.github.com/Font-Awesome)
|
||||
And of course to the Qt team/contributors for supplying this great cross-platform c++ library.
|
||||
|
||||
Contributions are welcome! Feel free to fork and send a pull request through Github.
|
Loading…
Reference in New Issue