Send to Cloud
An example showing how to upload a VTFx file to Ceetron Cloud (using Qt).
Get the upload ID from the user
The user needs to provide his/her unique upload ID to upload the file. The user will need an account on https://cloud.ceetron.com, so you need to send the user either to the https://cloud.ceetron.com/signup page or https://cloud.ceetron.com/login page. From the “My Account” page the user can press the button “Show and copy NN’s Upload ID” to get the upload ID. You should only ask for this once, and then store it somewhere safe in your app.
The Upload ID is a GUID, e.g.: 765ba418-58ea-4c0a-afd1-fb4ff8f410ee
Pass this to the example from the command prompt
> sendToCloud.exe -uploadId 'uploadId'
Export the model to a VTFx file.
Next, we need to create the VTFx file for upload. In this example we use the same model as in the Minimal Example. You should export to a temporary file, which should be deleted after upload.
Upload the file to Ceetron Cloud.
Use the following REST API call to upload the file to Ceetron:
API: %https://cloud.ceetron.com/api/v1/models
Params:
uploadId: The users’ Upload ID.
uploadApp: The name of your app.
name: The name of the model. This will become the default name in the My Models page of the user on Ceetron Cloud.
Example:
%https://cloud.ceetron.com/api/v1/models?uploadId=765ba418-58ea-4c0a-afd1-fb4ff8f410ee&uploadApp=My%20App&name=Demo%20Model
Note
The uploadApp and name parameters needs to be percent encoded to form a valid URI
The upload must be done with a “multipart/form-data” HTTPS POST, where the VTFx is sent as a file with the name “vtfx”.
Content-Disposition: form-data; name=\"vtfx\"; filename=\"upload.vtfx\"
Content-Type: application/octet-stream
QSCMain.cpp
QString cloudURL = "https://cloud.ceetron.com";
cee::PtrRef<cee::Instance> g_componentInstance;
//--------------------------------------------------------------------------
// Create the VTFx file
// -------------------------------------------------------------------------
cee::Str createVTFxFile()
{
cee::PtrRef<cee::vtfx::File> file = new cee::vtfx::File;
// Create VTFx file settings struct
cee::vtfx::FileSettings fileSettings;
fileSettings.applicationName = "VTFx: VTFxMinimal";
fileSettings.vendorName = "My Company AS";
// Let the API create the VTFx file
const cee::Str fileName = "ExampleMinimal.vtfx";
if (!file->create(fileName, fileSettings))
{
std::cout << "#ERROR: Could not create file." << std::endl;
return "";
}
// Create a single database (could have many)
cee::PtrRef<cee::vtfx::Database> database = new cee::vtfx::Database(file.get(), "Only Database", 1); // database id 1
// Create a single case (could have many)
cee::PtrRef<cee::vtfx::Case> singleCase = new cee::vtfx::Case(file.get(), "Only Case", 1, 1); // case id 1 using database with id 1
//--------------------------------------------------------------------------
// Add nodes
// -------------------------------------------------------------------------
// Create the node block
cee::PtrRef<cee::vtfx::NodeBlock> nodeBlock = new cee::vtfx::NodeBlock(1, false); // node block id 1, not using node ids
// Set the node data (Note: The VTFx component expects interleaved node coordinates (xyzxyzxyz...))
const float NODES_PART[] =
{
0.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
0.0f, 1.0f, 1.0f
};
std::vector<float> nodesPart(NODES_PART, NODES_PART + sizeof(NODES_PART) / sizeof(NODES_PART[0]));
if (!nodeBlock->setNodes(nodesPart))
{
std::cout << "#ERROR: Error setting node block." << std::endl;
return "";
}
// Write the block
if (!database->writeBlock(nodeBlock.get()))
{
std::cout << "#ERROR: Error writing node block." << std::endl;
return "";
}
//--------------------------------------------------------------------------
// Add elements
// -------------------------------------------------------------------------
// Create the element block
cee::PtrRef<cee::vtfx::ElementBlock> elementBlock = new cee::vtfx::ElementBlock(1, false, false); // element block id 1, not using element ids, referring nodes by index
elementBlock->setNodeBlockId(1); // Use node block with id = 1
// Set the element data
const int CONNECTS_PART[] =
{
0, 1, 2, 3, 4, 5, 6, 7
};
std::vector<int> elementNodes(CONNECTS_PART, CONNECTS_PART + sizeof(CONNECTS_PART) / sizeof(CONNECTS_PART[0]));
if (!elementBlock->addElements(cee::vtfx::ElementBlock::HEXAHEDRONS, elementNodes))
{
std::cout << "#ERROR: Error adding elements." << std::endl;
return "";
}
// Write the element block
if (!database->writeBlock(elementBlock.get()))
{
std::cout << "#ERROR: Error writing element block." << std::endl;
return "";
}
//--------------------------------------------------------------------------
// Add geometry
// -------------------------------------------------------------------------
// Create geometry block
cee::PtrRef<cee::vtfx::GeometryBlock> geoBlock = new cee::vtfx::GeometryBlock(1); // Only one geometry per state
size_t geoIndex = 0;
int partId = 1;
// Set to use element block with id 1
if (!geoBlock->addElementBlock(geoIndex, 1, partId)) // Element block with id = 1
{
std::cout << "#ERROR: Error adding element block." << std::endl;
return "";
}
// Write the geometry block
if (!database->writeBlock(geoBlock.get()))
{
std::cout << "#ERROR: Error writing geometry block." << std::endl;
return "";
}
// Create geometry info block
cee::PtrRef<cee::vtfx::GeometryInfoBlock> infoBlock = new cee::vtfx::GeometryInfoBlock(1); // Only one geometry per state
infoBlock->addPartInfo(geoIndex, partId, cee::Str("Only part"));
// Write the geometry info block
if (!database->writeBlock(infoBlock.get()))
{
std::cout << "#ERROR: Error writing info block." << std::endl;
return "";
}
//--------------------------------------------------------------------------
// Add one state
// -------------------------------------------------------------------------
// Create state info block
cee::PtrRef<cee::vtfx::StateInfoBlock> stepInfo = new cee::vtfx::StateInfoBlock;
if (!stepInfo->addStateInfo(1, "Only step", 42.0f, cee::vtfx::StateInfoBlock::TIME))
{
std::cout << "#ERROR: Error adding state info." << std::endl;
return "";
}
// Write the state info block
if (!database->writeBlock(stepInfo.get()))
{
std::cout << "#ERROR: Error writing state info block." << std::endl;
return "";
}
// Finally close the file
if (!file->close())
{
std::cout << "#ERROR: Error closing file." << std::endl;
return "";
}
std::cout << "#INFO: Exported successfully to file: " << fileName.toStdString() << std::endl;
return fileName;
}
//--------------------------------------------------------------------------------------------------
// SendToCloud app
//
// Usage:
// sendToCloud -uploadId MY_UPLOAD_ID
//--------------------------------------------------------------------------------------------------
int main(int argc, char *argv[])
{
// On Linux, Qt will use the system locale, force number formatting settings back to "C" locale
setlocale(LC_NUMERIC,"C");
QCoreApplication app(argc, argv);
CloudUploadTracker* uploadTracker = new CloudUploadTracker(&app, cloudURL);
// Initialize components and set up logging to console
// -------------------------------------------------------------------------
g_componentInstance = cee::CoreComponent::initialize(HOOPS_LICENSE);
cee::vtfx::VTFxComponent::initialize(g_componentInstance.get());
cee::PtrRef<cee::LogDestinationConsole> log = new cee::LogDestinationConsole();
cee::CoreComponent::logManager()->setDestination(log.get());
cee::CoreComponent::logManager()->setLevel("", 3); // Log errors, warnings and info
#ifndef QT_NO_OPENSSL
// Check that we have SSL/HTTPS support through Qt (normally provided via OpenSSL as a dynamic library)
if (!QSslSocket::supportsSsl())
{
std::cerr << "Your system and/or Qt version does not support secure connections (HTTPS).\nAre the OpenSSL dynamic libraries missing?" << std::endl;
return EXIT_FAILURE;
}
#else
// There is no compile time SSL support in this Qt version
// Either download another Qt version or recompile Qt with OpenSSl support
std::cerr << "Your Qt version was not built with OpenSSL support." << std::endl;
return EXIT_FAILURE;
#endif
QString uploadId = "";
// Parse command line parameters
QStringList arguments = app.arguments();
if (arguments.size() == 1)
{
std::cout << "sendToCloud v. 1.0-0" << std::endl << "---------------------" << std::endl << std::endl;
std::cout << "Note: App will upload to: " << cloudURL.toStdString() << std::endl << std::endl;
std::cout << "Usage:" << std::endl << std::endl;
std::cout << "> sendToCloud -uploadId MY_UPLOAD_ID" << std::endl << std::endl;
return EXIT_FAILURE;
}
int uploadIdIndex = -1;
for (int i = 1; i < arguments.size(); ++i)
{
if (uploadIdIndex == i)
{
uploadId = arguments[i];
uploadIdIndex = -1;
}
else if (arguments[i].toLower() == "-uploadid")
{
uploadIdIndex = i + 1;
}
}
// Check that all the needed info has been provided
if (uploadId.isEmpty())
{
std::cout << "No Upload ID provided (-uploadId)" << std::endl;
return EXIT_FAILURE;
}
cee::Str filename = createVTFxFile();
// We're ready to SendToCloud!
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
multiPart->setBoundary("boundary_Ceetron89906a4aJn1kvlAG52SFG2AD");
QHttpPart vtfxPart;
vtfxPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
vtfxPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"vtfx\" filename=\"upload.vtfx\""));
QFile *file = new QFile(cee::qt::UtilsCore::toQString(filename));
if (!file->open(QIODevice::ReadOnly))
{
std::cout << "Error opening file: " << filename.toStdString() << std::endl;
return EXIT_FAILURE;
}
vtfxPart.setBodyDevice(file);
file->setParent(multiPart);
multiPart->append(vtfxPart);
QString uploadApp = "VTFx Cloud Example";
QString caseName = "Minimal example";
QString urlString = QString(cloudURL + "/api/v1/models?uploadId=%1&uploadApp=%2&name=%3").arg(QString(QUrl::toPercentEncoding(uploadId))).arg(QString(QUrl::toPercentEncoding(uploadApp))).arg(QString(QUrl::toPercentEncoding(caseName)));
std::cout << "#INFO: Using URL: " << urlString.toStdString() << std::endl;
QUrl url(urlString);
QNetworkRequest request(url);
QNetworkAccessManager* manager = new QNetworkAccessManager(NULL);
QNetworkReply* reply = manager->post(request, multiPart);
multiPart->setParent(reply);
QObject::connect(reply, SIGNAL(finished()), uploadTracker, SLOT(cloudUploadFinished()));
QObject::connect(reply, SIGNAL(uploadProgress(qint64, qint64)), uploadTracker, SLOT(updateUploadProgress(qint64, qint64)));
QObject::connect(uploadTracker, SIGNAL(finished()), &app, SLOT(quit()));
std::cout << "#INFO: Uploading file to Ceetron Cloud..." << std::endl;
int result = app.exec();
std::cout << "#INFO: Done." << std::endl;
QSCCloudTracker.h
//--------------------------------------------------------------------------------------------------
/// Small class to track upload progress and completion
//--------------------------------------------------------------------------------------------------
class CloudUploadTracker : public QObject
{
Q_OBJECT
public:
CloudUploadTracker(QObject *parent, QString cloudURL) : QObject(parent), m_cloudURL(cloudURL) {}
public slots:
void cloudUploadFinished()
{
QNetworkReply* reply = (QNetworkReply*)sender();
const int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QString message;
if (httpStatusCode > 0)
{
QString responseJson = reply->readAll();
if (httpStatusCode == 200)
{
const QString modelKey = parseTopLevelJsonValue(responseJson, "modelKey");
const QString viewerUrl = m_cloudURL + "/v/" + modelKey;
message = "#SUCCESS;" + viewerUrl + "; model key:" + modelKey;
}
else
{
const QString failReason = parseTopLevelJsonValue(responseJson, "message");
const QString apiErrorCode = parseTopLevelJsonValue(responseJson, "apiErrorCode");
message = "#FAIL; Error: " + QString("%1 - httpStatusCode:%2 - apiErrorCode:%3").arg(failReason).arg(httpStatusCode).arg(apiErrorCode);
}
}
else
{
message = QString("#ERROR; Network Error During Upload - %1").arg(reply->errorString());
}
std::cout << message.toStdString() << std::endl;
reply->deleteLater();
emit finished();
}
void updateUploadProgress(qint64 bytesSent, qint64 bytesTotal)
{
float pctDone = 100.0f*(float)bytesSent/(float)bytesTotal;
std::cout << "#PROGRESS; " << pctDone << "; " << bytesSent << " of " << bytesTotal << " bytes. " << pctDone << "% done." << std::endl;
}
signals:
void finished();
private:
/// Static helper function to do crude parsing of top level JSON values
QString parseTopLevelJsonValue(const QString json, const QString key)
{
QString valString = "";
const QString quotedKey = QString("\"%1\"").arg(key);
const int keyIdx = json.indexOf(quotedKey);
if (keyIdx >= 0)
{
int startQuote = json.indexOf("\"", keyIdx + quotedKey.length());
int endQuote = json.indexOf("\"", startQuote + 1);
if (startQuote >= 0 && endQuote > (startQuote + 1))
{
valString = json.mid(startQuote + 1, endQuote - startQuote - 1);
}
}
return valString;
}
private:
QString m_cloudURL;
};