Send to Cloud

../../../_images/cloud_example.png

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;
};