Integrating Vortex Studio using the C++ SDK - Integrating the Application



This section targets developers who wants to use the Vortex Application as a part of a bigger application. This sections explains what is the VxApplication, how to configure it, load content into it and get it running from an external application.

Then how to feed Vortex with inputs,  as well as extracting data to feed the non vortex application.

VxApplication

 The VxApplication is the central object that contains modules and extensions; At every application update, those modules will read content objects and update them.

There can be only one instance of the VxApplication class per operating system process.
Additionally, in a given process' lifetime, a VxApplication instance should not be destroyed and replaced by a new instance. Proper cleanup isn't guaranteed.

Modules

VxApplication is initially empty, aside from internal objects its requires to run. Before loading content, it needs modules. Modules will update an aspect of the simulation by updating the extensions associated to it. The most common modules used by a Vortex Application are the Dynamics Module and the Graphics Module . Modules can be added or removed manually or by applying an application setup. Manual management requires the user to create modules using the extension factory and inserting in the application via the API. 

Example: Modules Insertion
...
Vx::VxSmartPtr<VxApplication> application = new VxApplication;
 
auto dynamicsModule = VxSmartInterface<ISimulatorModule>::create(VxDynamicsModuleICD::kFactoryKey);
application->insertModule(dynamicsModule);
...
// Run the simulation
...
application->removeModule(dynamicsModule);
...

The VxApplication API provides methods to insert, remove and enumerate modules.

Application Extensions

Extensions can also be added directly to the applications. Those extensions are not content but utility objects, such as UI extensions, profiling extensions, display extensions etc... Like modules, extensions can be added and removed manually or by applying a setup.  The VxApplication API also provides methods to add, remove, enumerate and search extensions.

Managing Extensions

When extensions are added to the application, either through loaded content or directly to the VxApplication object, they are sent to all modules. Each module will then decide if it manages that extension or not. For each extension added, each module has its method void onExtensionAdded(VxSim::VxExtension* extension) called. A specific module will usually check if the VxExtension in parameter responds to the IExtension/IObject subclasses it is interested in managing. To that end the template class VxSim::VxSmartInterface can be used to easily get a specific interface from an extension while ensuring that the object stays alive for as long as needed. If an extension will be managed by the module, the module must call VxSim::ISimulatorModule::_addManagedExtension().

Each modules also has an onExtensionRemoved(VxSim::VxExtension* extension) method, which is called when unloading content. A module that was managing a given extension must call VxSim::ISimulatorModule::_removeManagedExtension().

To get an array of all managed extensions inside a module, the module can call VxSim::VxSimulatorModule::getManagedExtensions().

void MyModule::onExtensionAdded(VxSim::VxExtension* extension)
{
    // add only extension of MyExtension interface
    VxSim::VxSmartInterface<MyExtension> myExt = extension;
    if (myExt.valid())
    {
        // adds the extension to the list of managed extensions
        _addManagedExtension(extension);
    }
}

void MyModule::onPostUpdate(VxSim::eApplicationMode mode)
{
    if ( mode == VxSim::kModeSimulating)
    {
		...

        const auto& extensions = getProxy()->getManagedExtensions();
        for (auto it = extensions.begin(); it != extensions.end(); ++it)
        {
            // get the MyExtension interface (should be valid since the module put it in the list)
            VxSim::VxSmartInterface<MyExtension> myExt = *it;
            myExt->updatePosition();
        }
    }
}

Using VxSim::VxSmartInterface implies a dynamic cast. If your module handles a lot of extensions and cast them often, it is preferable to keep them in a local data structure of the correct type.

std::vector<VxSim::VxSmartInterface<MyExtension>> mExtensions;

void MyModule::onExtensionAdded(VxSim::VxExtension* extension)
{
    VxSim::VxSmartInterface<MyExtension> myExt = extension;
    if (myExt.valid())
    {
        _addManagedExtension(extension);

		mExtensions.push_back(myExt);
	}
}

Application Parameters

The application have a few parameters such as frame rate, simulation time, logging etc.

These parameters can be defined using the application setup file.

Available options are

  • Logging: enableConsoleLog()/isConsoleLogEnabled()/setLogFilePrefix()

  • Frame Rate setSimulationFrameRate()/getSimulationFrameRate()/getSimulationTimeStep()

  • Synchronization Mode: setSyncMode()/getSyncMode()

  • Application Mode: setApplicationMode()/getApplicationMode()


In the case where the VxApplication is integrated into another application that controls the update rate and the wait time, the free running mode should be set to true : Vortex does not sleep during an update to match the frame rate.

VxApplication::enableFreeRunning and VxApplication::isFreeRunning are deprecated. Please use setSyncMode()/getSyncMode().


Application Setup

The Vortex Application can also be configured using an ApplicationConfig object. This object, called Application Setup in Vortex Studio, is a convenient way of inserting modules, extensions and setting up parameters for your application as they can be saved into files (.vxc) and reused. The setup files for the Vortex Studio application are located in the folder /resources/config from your Vortex Studio install location. 

Like everything Vortex, that Application setup can be filled in code, but the Vortex Studio Editor allows creation/edition of such objects under the Application Setup tab. 

A setup files is usually used to set up a multi-nodes simulators and setting additional options such as profiler settings, additional search path for plugins and seats. This will be covered in the section Vortex Studio SDK - Creating an application. 

To load an ApplicationConfig object from a file created by the Vortex Studio Editor, a developer have to use an ApplicationConfigSerializer and then extract the loaded setup to apply it on the VxApplication. Applying a configuration on a VxApplication will remove all modules and extensions, before applying your configuration. The ApplicationConfig default parameters values are the same as the default values for an empty VxApplication.

Example:Loading a setup file
...
Vx::VxSmartPtr<VxApplication> application = new VxApplication;
...
ApplicationConfigSerializer serializer;
 
// Load the setup file
if (!serializer.load("../Resources/assets/ExVHLIntegration/ExVHLIntegration.vxc"))
{
        Vx::LogError("Couldn't load application setup in ../Resources/assets/ExVHLIntegration/ExVHLIntegration.vxc.\n");
}

// Extract the ApplicationConfig
VxSmartInterface<ApplicationConfig> config = serializer.getApplicationConfig();
 
// Apply it
config->apply(application .get());
...

Loading Content

Before starting the application, content must be loaded into it so that the application will simulate something. The most convenient way of loading content is to use the VxSimulationFileManager provided by the VxApplication.

The VxSimulationFileManager will load the content into the application and ensure everything is properly dispatched to the application component.

Content is created using the Vortex Studio Editor, but also in code, c++ or python (see section Vortex Studio SDK, - Creating Content). It is possible to load multiples content files and move the object if needed; Most Dynamics Objects can be moved using the IMobile interface. It is preferable to set the application mode (see below) to Editing while loading content and moving some part of it around.

The simulation file manager can also be used to remove content (VxSimulationFileManager::unloadObject()),

Example:Loading a scene
...
Vx::VxSmartPtr<VxApplication> application = new VxApplication;
...
// Setup the application
...
// Get he file manager to load content
Vx::VxSmartPtr<VxSim::VxSimulationFileManager> fileManager = application ->getSimulationFileManager();


// Load the file, the object returned is the root object, in this case, a scene. The Reference should be kept to inspect the content during the simulation
VxSim::VxSmartInterface<VxContent::Scene> scene = fileManager->loadObject("../Resources/assets/ExVHLIntegration/Design/ExVHLIntegration.vxscene");
if (!scene.valid())
{
	Vx::LogError("Couldn't load the scene in ../Resources/assets/ExVHLIntegration/Design/ExVHLIntegration.vxscene.\n");
}
...
run the application
...
// Cleanup
fileManager->unloadObject(scene.getObject());
 

There is also a way to load content automatically at start up without any additional programming or user interface. To do this please refer to the Setup User Guide to learn more about using the Content Loader extension in your simulator's Setup document.

Running the Application

The are 2 main ways to run a Vortex Application:

  1. Calling function run()
    1. run() blocks the calling thread until the application stops. 
    2. It can be stopped from another thread by calling sendExitApplicationRequest().
  2. Calling function update() at each steps. 
    1. Function run() essentially calls update() in a while loop
    2. update() will sleep so that it takes no less than a time step in execution time, unless the application's synchronization mode is set to none or VSync.
    3. update() returns false when the application needs to stop.

Considering that this section is dedicated to integrating Vortex, you should set the synchronization mode to None and call update manually. 

Function update() should be called once per frame, no matter the simulation mode and also when the simulation is paused, as modules requires updates no matter the mode or the pause state.

Application Update

An application update consist of a call to VxApplication::update() and what happens outside of it. Note that the frame transition occurs during the update, but not exactly at the beginning.

Below is the sequence of code:

  1. Function VxApplication::Update() is called
  2. Application Requests made outside of VxApplication::Update() are processed
  3. If application is synchronized, the application waits here to match frame rate.
  4. Time is updated, Frame number is updated (frame transition  occurs here)
  5. If node is a slave, if updates the extensions with slave data
  6. Calling all ISimulatorModule::onPreUpdate()
  7. Calling all ISimulatorModule::onUpdate()
  8. Calling all ISimulatorModule::onPostUpdate()
  9. If node is master, extensions' data is gathered and sent to the slaves
  10. Application Requests made during the module phases are processed
  11. Code exits function VxApplication::Update()
  12. User code between call to update. In a Simapp process, there is nothing done

Thus, a single application update starts at step 4 and ends at step 3.

Application Modes

Application mode is part of the application context and is used by Modules and Extensions to make some decisions. Modules and extensions are aware of what the application is doing and can adapt their behavior. Implementations of ISimulatorModule and IApplicationListener are also informed of the transitions so they can prepare for the next mode.

There are 3 application modes in Vortex :

  • Editing mode: The simulation is not computed at every steps. That's when a user usually changes object's parameters, move objects, add or remove objects etc.
  • Simulating mode : The simulation is computed at every steps, objects parameters should not be modfied.  The simulation can be paused and resumed.
  • Playback mode : The simulation is not computed, objects are moved from the playback source so that content is replayed “graphically”.

The application mode is represent by the enum eApplicationMode. Setting the mode can take more than one update to be set, as some modules may requires additional update before mode can be changed. The Vortex Studio applications, such as the Editor and the Player, are using those modes. 

The Editor starts in editing mode. The user edits his content. When the user hits the play button, it goes is simulating mode. On stops, it goes back to editing. 

The Player also starts in editing mode. The user selects his content. When the user hits the play button, it goes is simulating mode. On stop, it goes back to editing. It the user loads a recording, the player goes in playback mode.

The default mode for a Vortex Application is simulating, it can be set to editing in the Application Setup.

Pausing the simulation

If the mode is set to simulation, the user can pause the simulation by calling pause().

Simulation can be resumed be calling resume().

Synchronization Modes

Synchronization mode indicates the application how to synchronize the simulation time with the real time. The synchronization mode is represented by the enum eSynchronizationMode. Setting the mode via the VxApplication will distribute de mode among all nodes in the simulator. It is asynchronous can take more than one update to be set. 

There are 4 modes available: 

  • No synchronization (kSyncNone) : All nodes go as fast as possible.
  • Vertical synchronization (kSyncVSync) : Application is synchronized with the monitor's refresh rate. VSync must be activated on the graphics card. VxApplication without a graphics module are synchronized as if there was no synchronization.
  • Software synchronization (kSyncSoftware): Synchronization is done by software. The VxApplication will wait between update to respect it's frame rate.
  • Software and vertical synchronization (kSyncSoftwareAndVSync) : Synchronization is done by vertical synchronization or by software as needed. This is the default mode.

Application Context

The ApplicationContext class contains basic information about the on-going simulation as well as giving you direct access to certain high-level services.

ApplicationContext API
/// Return true if the ApplicationContext belongs to a valid VxApplication
///
VXFOUNDATION_SYMBOL bool isInApplication() const;

/// Get the simulation frame rate in Hz (the inverse of the simulation time step)
///
VXFOUNDATION_SYMBOL double getSimulationFrameRate() const;

/// Get the simulation time step in seconds (the inverse of the simulation frame rate)
///
VXFOUNDATION_SYMBOL double getSimulationTimeStep() const;

/// Get the current simulation time in seconds 
///
VXFOUNDATION_SYMBOL double getSimulationTime() const;

/// Get the current cycle time in seconds 
///
VXFOUNDATION_SYMBOL double getCycleTime() const;

/// Get the time the application waited before processing the current update.
///
VXFOUNDATION_SYMBOL double getWaitTime() const;

/// Get the time taken by the last application update.
///
VXFOUNDATION_SYMBOL double getApplicationUpdateTime() const;


/// Gets the current frame index.
/// 
VXFOUNDATION_SYMBOL unsigned int getFrame() const;

/// Get the current mode from the application 
///
VXFOUNDATION_SYMBOL eApplicationMode getApplicationMode() const;

/// Get the current synchronization mode from the application 
///
VXFOUNDATION_SYMBOL eSynchronizationMode getSyncMode() const;

/// Get the name of the node used to configure the application
///
VXFOUNDATION_SYMBOL const std::string& getNodeName() const;

/// Check if the paused flag is set.  The pause flag is independent
/// from the application mode, but has an effect only in VxSim::kModeSimulating
///
VXFOUNDATION_SYMBOL bool isPaused() const;

///  Check if the application is currently simulating
/// @return true when isPaused() returns false and the mode is VxSim::kModeSimulating.
///
VXFOUNDATION_SYMBOL bool isSimulationRunning() const;

/// Return true if the VxApplication is currently doing an update()
///
VXFOUNDATION_SYMBOL bool isProcessingUpdate() const;

/// Returns the simulation thread id. The simulation thread is defined as the thread where VxApplication::update() is called.
///
VXFOUNDATION_SYMBOL size_t getSimulationThreadID() const;

/// get the error manager
///
VXFOUNDATION_SYMBOL ErrorManager* getErrorManager() const;

/// get the event manager
///
VXFOUNDATION_SYMBOL EventManager* getEventManager() const;

/// get the event manager
///
VXFOUNDATION_SYMBOL ControlPresetsManager* getControlPresetsManager() const;

/// get the profiler
///
VXFOUNDATION_SYMBOL Profiler* getProfiler() const;

/// get the Metrics Manager
///
VXFOUNDATION_SYMBOL MetricsManager* getMetricsManager() const;

/// get the KeyFrame Manager
///
VXFOUNDATION_SYMBOL VxContent::KeyFrameManager* getKeyFrameManager() const;

/// get the KeyFrame Manager
///
VXFOUNDATION_SYMBOL KinematicRecorder* getKinematicRecorder() const;

/// get the Simulator Monitor
///
VXFOUNDATION_SYMBOL SimulatorMonitor* getSimulatorMonitor() const;

/// get the Role Seat Manager
///
VXFOUNDATION_SYMBOL RoleSeatManager* getRoleSeatManager() const;


/// Execute the given function later in the simulation thread. The function will be called
/// at a safe moment during VxApplication::update(). executeLater() can be called in any thread.
/// If the context is not valid, the function is executed immediately.
///
/// @param function: the function to execute
///
/// @note The function is executed once. The functions are executed in the order they were put in.
///
VXFOUNDATION_SYMBOL void executeLater(std::function<void(void)> function) const;


You can get the ApplicationContext through the VxApplication and through your extensions.

  • VxApplication::getContext()
  • IExtension::getApplicationContext()
  • VxExtension::getApplicationContext()

The ApplicationContext returned from the VxApplication is, of course, always updated by the application. However, the object returned from an extension might or might not be updated by the application; it depends on whether or not the extension you got it from was added to the application. To know whether the ApplicationContext object you hold will be updated by the VxApplication, use the method ApplicationContext::isInApplication(). The ApplicationContext is valid between calls to callbacks IExtension::onActive() to IExtension::onInactive().

Here's a code sample demonstrating some simple uses of the ApplicationContext:

Is the ApplicationContext in the VxApplication
class SelfDrivingTruck : public VxSim::IExtension, public VxSim::IDynamics
{
public:
    SelfDrivingTruck(VxSim::VxExtension* proxy, int version)
        : VxSim::IExtension(proxy, version)
        , VxSim::IDynamics(proxy)
        , inputMaxAccelaration(1.0, "Max Acceleration", &proxy->getInputContainer())
		, mEventListener()
	{
    }

    ~SelfDrivingTruck()
    {
    }

    // From IExtension
    virtual void onActive() override
    {
		mEventListener = getApplicationContext().getEventManager()->registerListener(kEventId, [](const VxSim::EventData& data) {});
    }
	
	// From IExtension
	virtual void onInactive() override
    {
		mEventListener.unregister();
	}

    // From IDynamics
    virtual void preStep() override
    {
        ...
        // If the simulation frame rate is changed through VxApplication::setSimulationFrameRate(),
        // the ApplicationContext will be updated (the time step being the inverse of the frame rate) and we will get the new value
        // through the context the next time this is called.
        double velocityIncrement = inputMaxAccelaration * getApplicationContext().getSimulationTimeStep();
        ...
    }

    VxData::Field<double> inputMaxAccelaration;

private:
	EventListener mEventListener;
};

Application Context Usage

The ApplicationContext is separated into 3 main sections:

  1. The Application simulation information:
    1. isInApplication() - Indicates if the object accessing the context is in the application.
    2. getSimulationFrameRate() - The simulation frame rate, typically 60 fps
    3. getSimulationTimeStep() - The time steps in seconds i.e. 1/frame rate
    4. getSimulationTime() - The current simulation time. Simulation Time is only increased when simulation is running
    5. getCycleTime() - The current cycle time. The time it took between the last tick to the simulation time. This time should be very close to a time step.
    6. getWaitTime() - The time the application waited before processing the current update. To maintain a constant frame rate, the application will wait if you last update took less than a time step.
    7. getApplicationUpdateTime() - Time it took to process function VxApplication::update(). It includes the wait time.
    8. getFrame() - The current frame. Simulation Frame  is only increased when simulation is running
    9. getApplicationMode() - Current mode (see 2808842789)
    10. getSyncMode() - The the sync mode of this node
    11. getNodeName() - The node name of this application, as defined in the application setup.
    12. isPaused() - Indicates if the application is in mode simulating and paused.
    13. isSimulationRunning() - Indicates if the application is in mode simulating and running.

  2. The Application Services
    1. getErrorManager() - Get the error management services. Allows to set the log level, register a callback to get errors reported by any nodes.
    2. getEventManager() - Get the event manager, allowing users to send and receive events. (see Events)
    3. getControlPresetsManager() - Get the control presets manager. This is an asynchronous API that allows creation and edition of device mapping. (see Devices)
    4. getProfiler() - Get the profiling services for advanced debugging. (see tutorial ExDynamicsExtension for an example)
    5. getMetricsManager() - Get the Metrics Manager.
    6. getKeyFrameManager() - Get the Key frame manager. (see Key Frames)
    7. getKinematicRecorder() - Get the kinematic recording facilities. (see Record and Playback)
    8. getSimulatorMonitor() - Get the simulator monitor. It is an API that gives you information on the simulator
    9. getRoleSeatManager() - Get the API to manipulate role and seats.
    10. getRemoteAccessManager() - Get the Remote Access settings (see Remote Debugging)
  3. Threading services
    1. getSimulationThreadID() - Return the thread Id of the simulation thread.
    2. isProcessingUpdate() - Indicates if the simulation thread is in the process of calling VxApplication::update().
    3. executeLater() - Pass a function to be executed during the simulation thread. The given function will be called at a safe moment during a VxApplication::update().

Vortex Integration

When integrating Vortex, what a user usually want to do is to feed Vortex via its inputs, execute an update, then read back the outputs.

So the first thing to do is to get a reference to objects that are required to be driven and objects to read data from.

The are several ways to access objects from content. As shown in the code snippet above, when loading content, the user have access to the root object from the simulation file manager.

From that object, users can use the specific interface or the generic VxObject API to navigate the object hierarchy. To simplify navigation, a user should expose the data it wants to use via a VxVHLInterface at the scene or mechanism level.


Example - Loading content and getting the interfaces
const std::string kPaddle("Paddle");
const std::string kBall("Ball");
const std::string kInterface("Interface");
...
Vx::VxSmartPtr<VxApplication> application = new VxApplication;
...
// Setup the application
...
// Get he file manager to load content
Vx::VxSmartPtr<VxSim::VxSimulationFileManager> fileManager = application ->getSimulationFileManager();


// Load the file, the object returned is the root object, in this case, a scene. The Reference should be kept to inspect the content during the simulation
VxSim::VxSmartInterface<VxContent::Scene> scene = fileManager->loadObject("../Resources/assets/ExVHLIntegration/Design/ExVHLIntegration.vxscene");
if (!scene.valid())
{
	Vx::LogError("Couldn't load the scene in ../Resources/assets/ExVHLIntegration/Design/ExVHLIntegration.vxscene.\n");
}

// Save the mechanism references for easier manipulation and rendering.
auto mechanisms = scene->getMechanisms();
for (size_t i = 0; i < mechanisms.size(); ++i)
{
    auto mech = mechanisms[i];
    if (mech->getName() == kBall)
    {
    	mBall = mech.getObject()->findInterface(kInterface);
    }
    else if (mech->getName() == kPaddle)
    {
        mPaddle = mech.getObject()->findInterface(kInterface);
    }
}
...


Tutorials ExContentParsing shows how to use the Interface of Scenes, Mechanisms, Assemblies and Parts to navigate content.

Alternatively, a developer can use the IObject interface to search content under it. To search across all content, use the root object (such as the scene). The IObject interface offers some search functions to search by name (findExtensionByName()), by factory key (findExtensionByKey()) or by Interface (findExtensionByType()), recursively or not. 

VxApplication have function with similar behavior,  templated function findExtensions() and getExtensionCount()/getExtension(), but they are limited to extensions what was added directly to the application via function add(), or extensions added by an application setup. Content can be added directly to the application, that is what the VxSimulationFileManager does, but only the root object will show in queries.

Updating inputs and reading outputs

Once the reference to the required objects have been established, the external application basically need to update the inputs, execute an update and read the values from the outputs. Data should not be written into outputs. Parameters value should not change during simulation.

Getting data from content
const Vx::VxID kPositionId("Position");
const Vx::VxID kVelocityId("Velocity");

// See Loading content and getting the interface example above
...
Vx::VxSmartPtr<VxApplication> application = new VxApplication;
...
// Setup the application
...
// Load content a keep reference
...
 
// During the external application loop
... 
// Read input to feed to vortex
...
// Access the input Velocity from the only VHL Interface of the paddle mechanism and moves it at a speed of 4 unit per second in the given direction.
mPaddle->getInput(kVelocityId)->setValue(4. * double(direction));

// Updates the application's modules. In our case it only steps the dynamics engine.
if (!application ->update())
{
    mRunning = false; // The simulation was terminated, we're done.
}
    
// Get the ball mechanism position that by its interface Position output value.
Vx::VxVector3 pos = VxMath::Transformation::getTranslation( mBall->getOutput(kPositionId)->getValue<VxMath::Matrix44>() );
 
// Update external app
 


Vortex Remote

Client setup

The currently supported language and API for the client remotely querying the Vortex interfaces is Python with Google Protocol Buffers and Google RPC (gRPC). Users do not need to be well versed in gRPC to use Vortex Remote, but they should be familiar with Python. Users familiar with gRPC will be able to implement their client in other languages using Vortex's proto files. Advanced users can find Vortex's proto files under the Vortex Studio install location resources\proto folder.

To setup a Python client for querying the Vortex interfaces, users must first install the "vortex_remote" python package. It can be found under the Vortex Studio install location resources\pip_wheels folder as file vortex_remote-2019.7.0.0-py2.py3-none-any.whl.

Installation command
python -m pip install "resources\pip_wheels\vortex_remote-2019.7.0.0-py2.py3-none-any.whl"


Successful output

Processing resources\pip_wheels\vortex_remote-2020.5.0.0-py2.py3-none-any.whl
Collecting googleapis-common-protos>=1.5.3 (from vortex-remote==2020.5.0.0)
Downloading https://files.pythonhosted.org/packages/76/a6/903a1c1c1fd676660174a9be4ec777c9b547392f640b16a326182b8fe484/googleapis-common-protos-1.5.9.tar.gz
Collecting grpcio>=1.15.0 (from vortex-remote==2020.5.0.0)
Downloading https://files.pythonhosted.org/packages/4d/69/cae8de36aeab53b8b7e8d81bdf8cead1712c5481bbd1026a713d8011ef26/grpcio-1.19.0-cp27-cp27m-win_amd64.whl (1.6MB)
100% |################################| 1.6MB 6.8MB/s
Collecting protobuf>=3.6.1 (from vortex-remote==2020.5.0.0)
Downloading https://files.pythonhosted.org/packages/f5/e4/7c86c9c9b4bad7632e107064fb07d0812b9858a49830d0b2477081bf1267/protobuf-3.7.1-py2.py3-none-any.whl (404kB)
100% |################################| 409kB 9.3MB/s
Collecting grpcio-tools>=1.15.0 (from vortex-remote==2020.5.0.0)
Downloading https://files.pythonhosted.org/packages/0b/16/b09a2e01767e8a9403b074c477cd8ad268cbcf645f2450bfd087908e1523/grpcio_tools-1.19.0-cp27-cp27m-win_amd64.whl (1.7MB)
100% |################################| 1.7MB 7.9MB/s
Requirement already satisfied: futures>=2.2.0 in c:\programdata\anaconda2\lib\site-packages (from grpcio>=1.15.0->vortex-remote==2020.5.0.0)
Requirement already satisfied: enum34>=1.0.4 in c:\programdata\anaconda2\lib\site-packages (from grpcio>=1.15.0->vortex-remote==2020.5.0.0)
Requirement already satisfied: six>=1.5.2 in c:\programdata\anaconda2\lib\site-packages (from grpcio>=1.15.0->vortex-remote==2020.5.0.0)
Requirement already satisfied: setuptools in c:\programdata\anaconda2\lib\site-packages (from protobuf>=3.6.1->vortex-remote==2020.5.0.0)
Installing collected packages: protobuf, googleapis-common-protos, grpcio, grpcio-tools, vortex-remote
Running setup.py install for googleapis-common-protos ... done
Successfully installed googleapis-common-protos-1.5.9 grpcio-1.19.0 grpcio-tools-1.19.0 protobuf-3.7.1 vortex-remote-2020.5.0.0

Listing the available interfaces

Lets use a sample from the "Vortex Studio Samples 2020b" package as starting point. Specifically "Scenario/Forklift Scene/Forklift.vxscene". In the script, replace "localhost:10023" with the IP and port matching the Host Setup section. 

Print the list of interfaces
import sys
import grpc
from vortex.remote import vhl_pb2, vhl_pb2_grpc

if __name__ == '__main__':
  with grpc.insecure_channel('localhost:10023') as channel:
    stub = vhl_pb2_grpc.VHLStub(channel)
    interfaces = stub.List(vhl_pb2.Empty())
    print(str(interfaces))

Run your python script from your favorite IDE, or from the command line.

A failure like the following typically indicates that the python wheel was not installed. Review the Client Setup section.

Failure output

Traceback (most recent call last):
File "vhl_helloworld.py", line 3, in <module>
from vortex.remote import vhl_pb2, vhl_pb2_grpc
ImportError: No module named vortex.remote

A failure like the following typically indicates that the "RPC Module" was not added to your application or that you used a wrong IP:Port combo. Review the Host Setup section.

Failure output (2)

Traceback (most recent call last):
File "vhl_helloworld.py", line 9, in <module>
interfaces = stub.List(vortex.remote.vhl_pb2.Empty())
File "C:\Anaconda_4.3\lib\site-packages\grpc\_channel.py", line 547, in __call__
return _end_unary_response_blocking(state, call, False, None)
File "C:\Anaconda_4.3\lib\site-packages\grpc\_channel.py", line 466, in _end_unary_response_blocking
raise _Rendezvous(state, None, None, deadline)
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with:
status = StatusCode.UNAVAILABLE
details = "Connect Failed"
debug_error_string = "{"created":"@1544452952.955000000","description":"Failed to create subchannel","file":"src/core/ext/filters/client_channel/client_channel.cc","file_line":2706,"referenced_errors":[{"created":"@1544452952.955000000","description":"Pick Cancelled","file":"src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc","file_line":242,"referenced_errors":[{"created":"@1544452952.955000000","description":"Connect Failed","file":"src/core/ext/filters/client_channel/subchannel.cc","file_line":867,"grpc_status":14,"referenced_errors":[{"created":"@1544452952.955000000","description":"OS Error","file":"src/core/lib/iomgr/tcp_client_windows.cc","file_line":106,"os_error":"No connection could be made because the target machine actively refused it.\r\n","syscall":"ConnectEx","wsa_error":10061}]}]}]}"
>

.Proto files as documentation

Refer to "resources\proto\vhl.proto". This file describes the API to list and query the VHL interfaces. Specifically the stub.List() call of the previous "Print the list of interfaces" example.

...
message VHLListing
{
    repeated string path = 1;
}
service VHL
{
    rpc Hello(Empty) returns (Greeting) {}
    rpc List(Empty) returns (VHLListing) {}
    rpc Get(VHLRequest) returns (VHLInterface) {}
    rpc Set(VHLInterface) returns (Empty) {}
}

In the "Print the list of interfaces" example, "stub" was created from the "service VHL" section, and "stub.List()" was called passing an empty argument. We printed the list of interfaces from the returned "VHLListing" message.

The "path" member in "VHLListing" is iterable (repeated) like so:

...
interfaces = stub.List(vhl_pb2.Empty())
for interface in interfaces.path:
  print(interface)


Updating inputs and reading outputs

For this section, from "resources\proto\" refer "vhl.proto" and "field.proto". We'll continue building on the previous "Print the list of interfaces" example. We'll pick the simple forklift interface as an example.

Reading values
...
interface = stub.Get(vhl_pb2.VHLRequest(path = "vx://Forklift Scene/Forklift/Interface"))
print(interface)

Successful output

path: "vx://Forklift Scene/Forklift/Interface"
input {
name: "Direction"
doubleValue: 0.0
}
input {
name: "Speed"
doubleValue: -0.0
}
input {
name: "Brake"
doubleValue: 0.0
}
...
input {
name: "Speed Lever"
doubleValue: 1.0
}

Use typical python semantics to parse the returned "VHLInterface" message.

Setting values is the mirror operation. Instead of calling "stub.Get()" and receiving a "VHLInterface" message, call "stub.Set()" passing a new or modified "VHLInterface" message. We need to import "field_pb2", create a properly named "vhl_pb2.VHLInterface" and add the new value to give to "Speed Lever".

import sys
import grpc
from vortex.remote import field_pb2, vhl_pb2, vhl_pb2_grpc

if __name__ == '__main__':
  with grpc.insecure_channel('localhost:10023') as channel:
    stub = vhl_pb2_grpc.VHLStub(channel)
    interface = vhl_pb2.VHLInterface(path = "vx://Forklift Scene/Forklift/Interface")
    interface.input.extend([field_pb2.Field(name = "Speed Lever", doubleValue = 1.0)])
    stub.Set(interface)

If you execute this script while the simulation is running, you should see the forklift move forward once the new value is set.