mirror of
https://github.com/overte-org/overte.git
synced 2025-04-25 14:54:29 +02:00
Working on FBX uploads (i.e., without FST).
This commit is contained in:
parent
19b76e3331
commit
66e0cbf6ad
4 changed files with 205 additions and 98 deletions
|
@ -10,13 +10,18 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
#include <QFormLayout>
|
||||||
#include <QGridLayout>
|
#include <QGridLayout>
|
||||||
#include <QHttpMultiPart>
|
#include <QHttpMultiPart>
|
||||||
|
#include <QLineEdit>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QProgressBar>
|
#include <QProgressBar>
|
||||||
|
#include <QPushButton>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
#include <QTemporaryFile>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
|
||||||
|
@ -32,6 +37,7 @@ static const QString NAME_FIELD = "name";
|
||||||
static const QString FILENAME_FIELD = "filename";
|
static const QString FILENAME_FIELD = "filename";
|
||||||
static const QString TEXDIR_FIELD = "texdir";
|
static const QString TEXDIR_FIELD = "texdir";
|
||||||
static const QString LOD_FIELD = "lod";
|
static const QString LOD_FIELD = "lod";
|
||||||
|
static const QString JOINT_INDEX_FIELD = "jointIndex";
|
||||||
|
|
||||||
static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
|
static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
|
||||||
static const QString DATA_SERVER_URL = "https://data-web.highfidelity.io";
|
static const QString DATA_SERVER_URL = "https://data-web.highfidelity.io";
|
||||||
|
@ -76,7 +82,8 @@ bool ModelUploader::zip() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QString filename = QFileDialog::getOpenFileName(NULL, "Select your .fst file ...", lastLocation, "*.fst");
|
QString filename = QFileDialog::getOpenFileName(NULL, "Select your model file ...",
|
||||||
|
lastLocation, "Model files (*.fst *.fbx)");
|
||||||
if (filename == "") {
|
if (filename == "") {
|
||||||
// If the user canceled we return.
|
// If the user canceled we return.
|
||||||
Application::getInstance()->unlockSettings();
|
Application::getInstance()->unlockSettings();
|
||||||
|
@ -85,89 +92,112 @@ bool ModelUploader::zip() {
|
||||||
settings->setValue(SETTING_NAME, filename);
|
settings->setValue(SETTING_NAME, filename);
|
||||||
Application::getInstance()->unlockSettings();
|
Application::getInstance()->unlockSettings();
|
||||||
|
|
||||||
bool _nameIsPresent = false;
|
// First we check the FST file (if any)
|
||||||
QString texDir;
|
QFile* fst;
|
||||||
|
QVariantHash mapping;
|
||||||
|
QString basePath;
|
||||||
QString fbxFile;
|
QString fbxFile;
|
||||||
|
if (filename.toLower().endsWith(".fst")) {
|
||||||
|
fst = new QFile(filename, this);
|
||||||
|
if (!fst->open(QFile::ReadOnly | QFile::Text)) {
|
||||||
|
QMessageBox::warning(NULL,
|
||||||
|
QString("ModelUploader::zip()"),
|
||||||
|
QString("Could not open FST file."),
|
||||||
|
QMessageBox::Ok);
|
||||||
|
qDebug() << "[Warning] " << QString("Could not open FST file.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
qDebug() << "Reading FST file : " << QFileInfo(*fst).filePath();
|
||||||
|
mapping = readMapping(fst->readAll());
|
||||||
|
basePath = QFileInfo(*fst).path();
|
||||||
|
fbxFile = basePath + "/" + mapping.value(FILENAME_FIELD).toString();
|
||||||
|
QFileInfo fbxInfo(fbxFile);
|
||||||
|
if (!fbxInfo.exists() || !fbxInfo.isFile()) { // Check existence
|
||||||
|
QMessageBox::warning(NULL,
|
||||||
|
QString("ModelUploader::zip()"),
|
||||||
|
QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()),
|
||||||
|
QMessageBox::Ok);
|
||||||
|
qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(fbxInfo.fileName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fst = new QTemporaryFile(this);
|
||||||
|
fbxFile = filename;
|
||||||
|
basePath = QFileInfo(filename).path();
|
||||||
|
}
|
||||||
|
|
||||||
|
// open the fbx file
|
||||||
|
QFile fbx(fbxFile);
|
||||||
|
if (!fbx.open(QIODevice::ReadOnly)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QByteArray fbxContents = fbx.readAll();
|
||||||
|
FBXGeometry geometry = readFBX(fbxContents, QVariantHash());
|
||||||
|
|
||||||
// First we check the FST file
|
ModelPropertiesDialog properties(_isHead, mapping);
|
||||||
QFile fst(filename);
|
if (properties.exec() == QDialog::Rejected) {
|
||||||
if (!fst.open(QFile::ReadOnly | QFile::Text)) {
|
return false;
|
||||||
|
}
|
||||||
|
mapping = properties.getMapping();
|
||||||
|
|
||||||
|
QByteArray nameField = mapping.value(NAME_FIELD).toByteArray();
|
||||||
|
if (!nameField.isEmpty()) {
|
||||||
|
QHttpPart textPart;
|
||||||
|
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"model_name\"");
|
||||||
|
textPart.setBody(nameField);
|
||||||
|
_dataMultiPart->append(textPart);
|
||||||
|
_url = S3_URL + ((_isHead)? "/models/heads/" : "/models/skeletons/") + nameField + ".fst";
|
||||||
|
} else {
|
||||||
QMessageBox::warning(NULL,
|
QMessageBox::warning(NULL,
|
||||||
QString("ModelUploader::zip()"),
|
QString("ModelUploader::zip()"),
|
||||||
QString("Could not open FST file."),
|
QString("Model name is missing in the .fst file."),
|
||||||
QMessageBox::Ok);
|
QMessageBox::Ok);
|
||||||
qDebug() << "[Warning] " << QString("Could not open FST file.");
|
qDebug() << "[Warning] " << QString("Model name is missing in the .fst file.");
|
||||||
return false;
|
|
||||||
}
|
|
||||||
qDebug() << "Reading FST file : " << QFileInfo(fst).filePath();
|
|
||||||
|
|
||||||
// Compress and copy the fst
|
|
||||||
if (!addPart(QFileInfo(fst).filePath(), QString("fst"))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's read through the FST file
|
QByteArray texdirField = mapping.value(TEXDIR_FIELD).toByteArray();
|
||||||
QTextStream stream(&fst);
|
QString texDir;
|
||||||
QList<QString> line;
|
if (!texdirField.isEmpty()) {
|
||||||
while (!stream.atEnd()) {
|
texDir = basePath + "/" + texdirField;
|
||||||
line = stream.readLine().split(QRegExp("[ =]"), QString::SkipEmptyParts);
|
QFileInfo texInfo(texDir);
|
||||||
if (line.isEmpty()) {
|
if (!texInfo.exists() || !texInfo.isDir()) {
|
||||||
continue;
|
QMessageBox::warning(NULL,
|
||||||
}
|
QString("ModelUploader::zip()"),
|
||||||
|
QString("Texture directory could not be found."),
|
||||||
// according to what is read, we modify the command
|
QMessageBox::Ok);
|
||||||
if (line[0] == NAME_FIELD) {
|
qDebug() << "[Warning] " << QString("Texture directory could not be found.");
|
||||||
QHttpPart textPart;
|
return false;
|
||||||
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"
|
|
||||||
" name=\"model_name\"");
|
|
||||||
textPart.setBody(line[1].toUtf8());
|
|
||||||
_dataMultiPart->append(textPart);
|
|
||||||
_url = S3_URL + ((_isHead)? "/models/heads/" : "/models/skeletons/") + line[1].toUtf8() + ".fst";
|
|
||||||
_nameIsPresent = true;
|
|
||||||
} else if (line[0] == FILENAME_FIELD) {
|
|
||||||
fbxFile = QFileInfo(fst).path() + "/" + line[1];
|
|
||||||
QFileInfo fbxInfo(fbxFile);
|
|
||||||
if (!fbxInfo.exists() || !fbxInfo.isFile()) { // Check existence
|
|
||||||
QMessageBox::warning(NULL,
|
|
||||||
QString("ModelUploader::zip()"),
|
|
||||||
QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()),
|
|
||||||
QMessageBox::Ok);
|
|
||||||
qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(fbxInfo.fileName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Compress and copy
|
|
||||||
if (!addPart(fbxInfo.filePath(), "fbx")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (line[0] == TEXDIR_FIELD) { // Check existence
|
|
||||||
texDir = QFileInfo(fst).path() + "/" + line[1];
|
|
||||||
QFileInfo texInfo(texDir);
|
|
||||||
if (!texInfo.exists() || !texInfo.isDir()) {
|
|
||||||
QMessageBox::warning(NULL,
|
|
||||||
QString("ModelUploader::zip()"),
|
|
||||||
QString("Texture directory could not be found."),
|
|
||||||
QMessageBox::Ok);
|
|
||||||
qDebug() << "[Warning] " << QString("Texture directory could not be found.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (line[0] == LOD_FIELD) {
|
|
||||||
QFileInfo lod(QFileInfo(fst).path() + "/" + line[1]);
|
|
||||||
if (!lod.exists() || !lod.isFile()) { // Check existence
|
|
||||||
QMessageBox::warning(NULL,
|
|
||||||
QString("ModelUploader::zip()"),
|
|
||||||
QString("LOD file %1 could not be found.").arg(lod.fileName()),
|
|
||||||
QMessageBox::Ok);
|
|
||||||
qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(lod.fileName());
|
|
||||||
}
|
|
||||||
// Compress and copy
|
|
||||||
if (!addPart(lod.filePath(), QString("lod%1").arg(++_lodCount))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!addTextures(texDir, fbxFile)) {
|
QVariantHash lodField = mapping.value(LOD_FIELD).toHash();
|
||||||
|
for (QVariantHash::const_iterator it = lodField.constBegin(); it != lodField.constEnd(); it++) {
|
||||||
|
QFileInfo lod(basePath + "/" + it.key());
|
||||||
|
if (!lod.exists() || !lod.isFile()) { // Check existence
|
||||||
|
QMessageBox::warning(NULL,
|
||||||
|
QString("ModelUploader::zip()"),
|
||||||
|
QString("LOD file %1 could not be found.").arg(lod.fileName()),
|
||||||
|
QMessageBox::Ok);
|
||||||
|
qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(lod.fileName());
|
||||||
|
}
|
||||||
|
// Compress and copy
|
||||||
|
if (!addPart(lod.filePath(), QString("lod%1").arg(++_lodCount))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write out, compress and copy the fst
|
||||||
|
if (!addPart(*fst, writeMapping(mapping), QString("fst"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compress and copy the fbx
|
||||||
|
if (!addPart(fbx, fbxContents, "fbx")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!addTextures(texDir, geometry)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,15 +211,6 @@ bool ModelUploader::zip() {
|
||||||
}
|
}
|
||||||
_dataMultiPart->append(textPart);
|
_dataMultiPart->append(textPart);
|
||||||
|
|
||||||
if (!_nameIsPresent) {
|
|
||||||
QMessageBox::warning(NULL,
|
|
||||||
QString("ModelUploader::zip()"),
|
|
||||||
QString("Model name is missing in the .fst file."),
|
|
||||||
QMessageBox::Ok);
|
|
||||||
qDebug() << "[Warning] " << QString("Model name is missing in the .fst file.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_readyToSend = true;
|
_readyToSend = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -350,25 +371,16 @@ void ModelUploader::processCheck() {
|
||||||
delete reply;
|
delete reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModelUploader::addTextures(const QString& texdir, const QString fbxFile) {
|
bool ModelUploader::addTextures(const QString& texdir, const FBXGeometry& geometry) {
|
||||||
QFile fbx(fbxFile);
|
|
||||||
if (!fbx.open(QIODevice::ReadOnly)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray buffer = fbx.readAll();
|
|
||||||
QVariantHash variantHash = readMapping(buffer);
|
|
||||||
FBXGeometry geometry = readFBX(buffer, variantHash);
|
|
||||||
|
|
||||||
foreach (FBXMesh mesh, geometry.meshes) {
|
foreach (FBXMesh mesh, geometry.meshes) {
|
||||||
foreach (FBXMeshPart part, mesh.parts) {
|
foreach (FBXMeshPart part, mesh.parts) {
|
||||||
if (!part.diffuseTexture.filename.isEmpty()) {
|
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty()) {
|
||||||
if (!addPart(texdir + "/" + part.diffuseTexture.filename,
|
if (!addPart(texdir + "/" + part.diffuseTexture.filename,
|
||||||
QString("texture%1").arg(++_texturesCount))) {
|
QString("texture%1").arg(++_texturesCount))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!part.normalTexture.filename.isEmpty()) {
|
if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty()) {
|
||||||
if (!addPart(texdir + "/" + part.normalTexture.filename,
|
if (!addPart(texdir + "/" + part.normalTexture.filename,
|
||||||
QString("texture%1").arg(++_texturesCount))) {
|
QString("texture%1").arg(++_texturesCount))) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -390,7 +402,11 @@ bool ModelUploader::addPart(const QString &path, const QString& name) {
|
||||||
qDebug() << "[Warning] " << QString("Could not open %1").arg(path);
|
qDebug() << "[Warning] " << QString("Could not open %1").arg(path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QByteArray buffer = qCompress(file.readAll());
|
return addPart(file, file.readAll(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const QString& name) {
|
||||||
|
QByteArray buffer = qCompress(contents);
|
||||||
|
|
||||||
// Qt's qCompress() default compression level (-1) is the standard zLib compression.
|
// Qt's qCompress() default compression level (-1) is the standard zLib compression.
|
||||||
// Here remove Qt's custom header that prevent the data server from uncompressing the files with zLib.
|
// Here remove Qt's custom header that prevent the data server from uncompressing the files with zLib.
|
||||||
|
@ -420,8 +436,45 @@ bool ModelUploader::addPart(const QString &path, const QString& name) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModelPropertiesDialog::ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping) :
|
||||||
|
_originalMapping(originalMapping) {
|
||||||
|
|
||||||
|
setWindowTitle("Set Model Properties");
|
||||||
|
|
||||||
|
QFormLayout* form = new QFormLayout();
|
||||||
|
setLayout(form);
|
||||||
|
|
||||||
|
form->addRow("Name:", _name = new QLineEdit());
|
||||||
|
form->addRow("Texture Directory:", _textureDirectory = new QPushButton());
|
||||||
|
connect(_textureDirectory, SIGNAL(clicked(bool)), SLOT(chooseTextureDirectory()));
|
||||||
|
|
||||||
|
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok |
|
||||||
|
QDialogButtonBox::Cancel | QDialogButtonBox::Reset);
|
||||||
|
connect(buttons, SIGNAL(accepted()), SLOT(accept()));
|
||||||
|
connect(buttons, SIGNAL(rejected()), SLOT(reject()));
|
||||||
|
connect(buttons->button(QDialogButtonBox::Reset), SIGNAL(clicked(bool)), SLOT(reset()));
|
||||||
|
|
||||||
|
form->addRow(buttons);
|
||||||
|
|
||||||
|
// reset to initialize the fields
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantHash ModelPropertiesDialog::getMapping() const {
|
||||||
|
QVariantHash mapping = _originalMapping;
|
||||||
|
mapping.insert(NAME_FIELD, _name->text());
|
||||||
|
mapping.insert(TEXDIR_FIELD, _textureDirectory->text());
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelPropertiesDialog::reset() {
|
||||||
|
_name->setText(_originalMapping.value(NAME_FIELD).toString());
|
||||||
|
_textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelPropertiesDialog::chooseTextureDirectory() {
|
||||||
|
QString directory = QFileDialog::getExistingDirectory(this, "Choose Texture Directory", _textureDirectory->text());
|
||||||
|
if (!directory.isEmpty()) {
|
||||||
|
_textureDirectory->setText(directory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,12 +12,16 @@
|
||||||
#ifndef hifi_ModelUploader_h
|
#ifndef hifi_ModelUploader_h
|
||||||
#define hifi_ModelUploader_h
|
#define hifi_ModelUploader_h
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
class QDialog;
|
|
||||||
class QFileInfo;
|
class QFileInfo;
|
||||||
class QHttpMultiPart;
|
class QHttpMultiPart;
|
||||||
|
class QLineEdit;
|
||||||
class QProgressBar;
|
class QProgressBar;
|
||||||
|
class QPushButton;
|
||||||
|
|
||||||
|
class FBXGeometry;
|
||||||
|
|
||||||
class ModelUploader : public QObject {
|
class ModelUploader : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -56,8 +60,28 @@ private:
|
||||||
|
|
||||||
|
|
||||||
bool zip();
|
bool zip();
|
||||||
bool addTextures(const QString& texdir, const QString fbxFile);
|
bool addTextures(const QString& texdir, const FBXGeometry& geometry);
|
||||||
bool addPart(const QString& path, const QString& name);
|
bool addPart(const QString& path, const QString& name);
|
||||||
|
bool addPart(const QFile& file, const QByteArray& contents, const QString& name);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A dialog that allows customization of various model properties.
|
||||||
|
class ModelPropertiesDialog : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping);
|
||||||
|
|
||||||
|
QVariantHash getMapping() const;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void reset();
|
||||||
|
void chooseTextureDirectory();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QVariantHash _originalMapping;
|
||||||
|
QLineEdit* _name;
|
||||||
|
QPushButton* _textureDirectory;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ModelUploader_h
|
#endif // hifi_ModelUploader_h
|
||||||
|
|
|
@ -1803,6 +1803,33 @@ QVariantHash readMapping(const QByteArray& data) {
|
||||||
return parseMapping(&buffer);
|
return parseMapping(&buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray writeMapping(const QVariantHash& mapping) {
|
||||||
|
QBuffer buffer;
|
||||||
|
buffer.open(QIODevice::WriteOnly);
|
||||||
|
for (QVariantHash::const_iterator first = mapping.constBegin(); first != mapping.constEnd(); first++) {
|
||||||
|
QByteArray key = first.key().toUtf8() + " = ";
|
||||||
|
QVariantHash hashValue = first.value().toHash();
|
||||||
|
if (hashValue.isEmpty()) {
|
||||||
|
buffer.write(key + first.value().toByteArray() + "\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (QVariantHash::const_iterator second = hashValue.constBegin(); second != hashValue.constEnd(); second++) {
|
||||||
|
QByteArray extendedKey = key + second.key().toUtf8();
|
||||||
|
QVariantList listValue = second.value().toList();
|
||||||
|
if (listValue.isEmpty()) {
|
||||||
|
buffer.write(extendedKey + " = " + second.value().toByteArray() + "\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
buffer.write(extendedKey);
|
||||||
|
for (QVariantList::const_iterator third = listValue.constBegin(); third != listValue.constEnd(); third++) {
|
||||||
|
buffer.write(" = " + third->toByteArray());
|
||||||
|
}
|
||||||
|
buffer.write("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer.data();
|
||||||
|
}
|
||||||
|
|
||||||
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping) {
|
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping) {
|
||||||
QBuffer buffer(const_cast<QByteArray*>(&model));
|
QBuffer buffer(const_cast<QByteArray*>(&model));
|
||||||
buffer.open(QIODevice::ReadOnly);
|
buffer.open(QIODevice::ReadOnly);
|
||||||
|
|
|
@ -218,6 +218,9 @@ Q_DECLARE_METATYPE(FBXGeometry)
|
||||||
/// Reads an FST mapping from the supplied data.
|
/// Reads an FST mapping from the supplied data.
|
||||||
QVariantHash readMapping(const QByteArray& data);
|
QVariantHash readMapping(const QByteArray& data);
|
||||||
|
|
||||||
|
/// Writes an FST mapping to a byte array.
|
||||||
|
QByteArray writeMapping(const QVariantHash& mapping);
|
||||||
|
|
||||||
/// Reads FBX geometry from the supplied model and mapping data.
|
/// Reads FBX geometry from the supplied model and mapping data.
|
||||||
/// \exception QString if an error occurs in parsing
|
/// \exception QString if an error occurs in parsing
|
||||||
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping);
|
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping);
|
||||||
|
|
Loading…
Reference in a new issue