Vortex Studio SDK: Integrating the Application

This section targets developers who want to use the Vortex® Application as a part of a bigger application.

Note
If using Vortex Studio Academic edition, you cannot create a custom executable. You must use a built-in executable such as SimApp.exe or Vortex Studio Player.

This section explains what VxApplication is, how to configure it, load content into it and get it running from an external application, as well as feeding Vortex with inputs and extracting data to feed the non-Vortex application. (Please look at the tutorial Integrating the Application. This section will refer to it as an example.)

Topics

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.

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

Modules

A VxApplication is initially empty, except for internal objects it is required 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 Modules. Modules can be added or removed manually or by applying an Integrating the Application. 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 the 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 module 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 casts 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 has a few parameters such as frame rate, simulation time, logging etc. These parameters can be defined using the Application Setup file.

Available options are:

In the case where the VxApplication is integrated into another application that controls the update rate and wait time, a user would most likely want to enable the free running mode rather than let Vortex sleep during an update to match the frame rate. Most of those settings can also be fed by the application setup.

Note
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 (with the VXC extension) and re-used. The setup files for the Vortex Studio application are located in the folder /resouces/config from your Vortex Studio installation location.

Like everything Vortex, Application setup can be done in code, but the Vortex Studio Editor allows creation and editing of such objects under the Application Setup tab.

A setup file is usually used to set up multi-node simulators and setting additional options such as Profiler settings, additional search paths for plugins and seats. This will be covered in the section Creating an Application.

To load an ApplicationConfig object from a file created by the Vortex Studio Editor, a developer has to use an ApplicationConfigSerializer and then extract the loaded setup to apply it to 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 Creating Content).

Example: Loading a Scene
...
Vx::VxSmartPtr<VxApplication> application = new VxApplication;
...
// Setup the application
...
// Get the 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());

It is possible to load multiple 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 parts of it around.

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

Running the Application

The are two 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 step.
    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.

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

Application Modes

Application mode is part of the application context and is used by modules and extensions to make certain 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 three application modes in Vortex:

  • Editing mode: The simulation is not computed at every step. This is when a user usually changes the object's parameters, moves objects, adds or removes objects, etc.
  • Simulating mode: The simulation is computed at every step, object parameters should not be modified. 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 require additional updates before mode can be changed. The Vortex Studio applications, such as the Editor and the Player, use those modes.

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

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

The default mode for a Vortex Application is simulating, but 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

The synchronization mode instructs the application on 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 the mode among all nodes in the simulator; it is asynchronous can take more than one update to be set.

The following modes are 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. VxApplications 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 updates to respect its frame rate.
  • Software and vertical synchronization (kSyncSoftwareAndVSync): Combines VxApplication software synchronization with vertical synchronization. This the default mode.
Note
Vortex Studio 2019a deprecates the Application Config "Enable Free Running" flag and graphics module "Enable VSync". It is replaced at load time following this convention:
  • Enable Free Running = true and Enable VSync = true: kSyncVSync
  • Enable Free Running = false and Enable VSync = true: kSyncSoftwareAndVSync
  • Enable Free Running = true and Enable VSync = false: kSyncNone
  • Enable Free Running = false and Enable VSync = false: kSyncSoftware

Application Context

The ApplicationContext class contains basic information about the ongoing 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 ApplicationContext through the VxApplication and through your extensions.

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().

The following is 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)
		, inputMaxAcceleration(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 these main sections:

  1. The Application simulation information:
    1. getSimulationFrameRate():The simulation frame rate, typically 60 FPS.
    2. getSimulationTimeStep(): The time steps in seconds, i.e., 1/framerate.
    3. getSimulationTime(): The current simulation time. Simulation Time is only increased when simulation is running.
    4. 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.
    5. getWaitTime(): The time the application waited before processing the current update. To maintain a constant frame rate, the application will wait if your last update took less than a time step.
    6. getApplicationUpdateTime(): Time it took to process function VxApplication::update(). It includes the wait time.
    7. getFrame() The current frame. Simulation Frame is only increased when simulation is running.
    8. getApplicationMode(): Current mode (see Application Modes).
    9. getSyncMode(): The sync mode of this node.
    10. getNodeName(): The node name of this application, as defined in the application setup.
    11. isPaused(): Indicates whether the application is in simulating mode and paused.
    12. isSimulationRunning(): Indicates whether the application is in simulating mode 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 editing 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.
  3. Threading services
    1. getSimulationThreadID(): Return the thread ID of the simulation thread.
    2. isProcessingUpdate(): Indicates whether 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 wants to do is to feed Vortex via its inputs, execute an update, then read the outputs back. 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 has 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 they want to use via a VxVHLInterface at the scene or mechanism level.

The ExVHLIntegration tutorial keeps the VHLInterface of the ball and paddle.

Example: ExVHLIntegration - Getting Data from Content
const Vx::VxID kPositionId("Position");
const Vx::VxID kVelocityId("Velocity");
const std::string kPaddle("Paddle");
const std::string kBall("Ball");
const std::string kInterface("Interface");
...
Vx::VxSmartPtr<VxApplication> application = new VxApplication;
...
// Set up the application
...
// Get the 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);
	}
}
...

Tutorial 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 has functions with similar behavior, template functions findExtensions() and getExtensionCount()/getExtension(), but they are limited to extensions that were added directly to the application via add(), or extensions added by an application setup. Content can be added directly to the application (which is what the VxSimulationFileManager does) but only the root object will show in queries.

Updating Inputs and Reading Outputs

Once the references to the required objects have been established, the external application needs to update the inputs, execute an update and read the values from the outputs. Data should not be written into outputs. Parameter values should not change during simulation.

Example: Getting Data from Content
...
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 units per second in the given direction.
mPaddle->getInput(kVelocityId)->setValue(4. * double(dir));

// 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

Host Setup

Vortex Studio allows users to create a remote connection to feed inputs and read outputs of VHL interfaces from outside of the simulation. This works for any Vortex® Studio application provided it was set up with an RPC Module.

RPC module icon

This module has a single parameter, the Listening Port. By default, the module listens on "0.0.0.0:10023". Which means it accepts all incoming connections on all network adapters, for specific port 10023. Any Vortex Studio application set up with an RPC Module will allow the user to query the list of VHL interfaces present in the simulation, query their fields and values and feed new values. Your firewall, network and operating system settings may prevent you from connecting. Be mindful of your networking setup: the remote connection uses no encryption and no authentication.

Client Setup

The currently supported language and API for the client remotely querying the VHL 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 Studio's VHL proto files. Advanced users can find Vortex Studio's VHL proto files under the resources\proto folder in the Vortex Studio installation location.

To set up a Python client for querying the VHL interfaces, users must first install the vortex_remote_vhl-current_version-py2-none-any.whl Python package. It can be found under the resources\pip_wheels folder Vortex Studio installation location as file vortex_remote_vhl-current_version-py2-none-any.whl.

Installation Command
python -m pip install "resources\pip_wheels\vortex_remote_vhl-current_version-py2-none-any.whl"
Successful Output
Processing resources\pip_wheels\vortex_remote_vhl-current_version-py2-none-any.whl
Collecting googleapis-common-protos>=1.5.3 (from vortex-remote-vhl==current_version)
Collecting grpcio>=1.15.0 (from vortex-remote-vhl==current_version)
 Downloading https://files.pythonhosted.org/packages/cb/82/b97a2683ce884e01b9c0833731c0670d6d61b183deb841c177f7a8236976
  /grpcio-1.17.0-cp27-cp27m-win_amd64.whl (1.5MB)
 100% |################################| 1.5MB 984kB/s
Collecting protobuf>=3.6.1 (from vortex-remote-vhl==current_version)
 Using cached https://files.pythonhosted.org/packages/77/78/a7f1ce761e2c738e209857175cd4f90a8562d1bde32868a8cd5290d58926
  /protobuf-3.6.1-py2.py3-none-any.whl
Collecting grpcio-tools>=1.15.0 (from vortex-remote-vhl==current_version)
 Downloading https://files.pythonhosted.org/packages/8f/33/49981d4e7d40d10aa776ace55e2842e0deb8e5f6748c7c68cb873844e609
  /grpcio_tools-1.17.0-cp27-cp27m-win_amd64.whl (1.7MB)
 100% |################################| 1.7MB 890kB/s
Requirement already satisfied: futures>=2.2.0 in c:\anaconda_4.3\lib\site-packages (from grpcio>=1.15.0->vortex-remote-vhl==current_version)
Requirement already satisfied: enum34>=1.0.4 in c:\anaconda_4.3\lib\site-packages (from grpcio>=1.15.0->vortex-remote-vhl==current_version)
Requirement already satisfied: six>=1.5.2 in c:\anaconda_4.3\lib\site-packages (from grpcio>=1.15.0->vortex-remote-vhl==current_version)
Requirement already satisfied: setuptools in c:\users\userXYZ\appdata\roaming\python\python27\site-packages 
 (from protobuf>=3.6.1->vortex-remote-vhl==current_version)
Installing collected packages: protobuf, googleapis-common-protos, grpcio, grpcio-tools, vortex-remote-vhl
Successfully installed googleapis-common-protos-1.5.5 grpcio-1.17.0 grpcio-tools-1.17.0 protobuf-3.6.1 vortex-remote-vhl-current_version

Listing the Available Interfaces

In the following example, use Scenario/Forklift Scene/Forklift.vxscene found in your Vortex Studio Demo Scenes. 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.

Successful Output
path: "vx://Forklift Scene/Warehouse/Warehouse/Interface"
path: "vx://Forklift Scene/Forklift/Gallery/Interface"
path: "vx://Forklift Scene/Forklift/Interface"

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 combination. 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 found in the Vortex Studio installation location. 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 above.

...
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)
Successful Output
vx://Forklift Scene/Warehouse/Warehouse/Interface
vx://Forklift Scene/Forklift/Gallery/Interface
vx://Forklift Scene/Forklift/Interface

Updating Inputs and Reading Outputs

For this example, refer to vhl.proto and field.proto found in the resources\proto folder of the Vortex Studio installation location, while also building upon the previous "Print the List of Interfaces" example, picking 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 a mirror operation to the above. Instead of calling stub.Get() and receiving a "VHLInterface" message, call stub.Set(), passing a new or modified "VHLInterface" message. Iimport field_pb2, create a properly named vhl_pb2.VHLInterface and add the new value to give to the "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.

 

Next topic: Adding User Controls