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

Conversation saved
HOOPSY BETA

### Hello! I'm HOOPSY

Your multilingual generative AI assistant for documentation. Ask me anything about HOOPS Envision for Desktop in your own language, or choose a sample question below to start a conversation:

What is HOOPS Envision for Desktop?
What are the latest release notes for HOOPS Envision for Desktop?
How do I start evaluating HOOPS Envision for Desktop?
How do I write my first sample application?
How can I get assistance?

HOOPSY may store conversations according to OpenAI's policy. Responses may not always be accurate.

HOOPSY is thinking...