Vortex Studio SDK Advanced - 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
A 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.
... 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
.
... 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()
),
... 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:
- Calling function
run()
.run()
blocks the calling thread until the application stops.- It can be stopped from another thread by calling
sendExitApplicationRequest()
.
- Calling function
update()
at each steps.- Function
run()
essentially callsupdate()
in a while loop 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.update()
returns false when the application needs to stop.
- Function
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:
- Function VxApplication::Update() is called
- Application Requests made outside of VxApplication::Update() are processed
- If application is synchronized, the application waits here to match frame rate.
- Time is updated, Frame number is updated (frame transition occurs here)
- If node is a slave, if updates the extensions with slave data
- Calling all ISimulatorModule::onPreUpdate()
- Calling all ISimulatorModule::onUpdate()
- Calling all ISimulatorModule::onPostUpdate()
- If node is master, extensions' data is gathered and sent to the slaves
- Application Requests made during the module phases are processed
- Code exits function VxApplication::Update()
- 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. TheVxApplication
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.
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
:
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:
- The Application simulation information:
- isInApplication() - Indicates if the object accessing the context is in the application.
- getSimulationFrameRate() - The simulation frame rate, typically 60 fps
- getSimulationTimeStep() - The time steps in seconds i.e. 1/frame rate
- getSimulationTime() - The current simulation time. Simulation Time is only increased when simulation is running
- 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.
- 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.
- getApplicationUpdateTime() - Time it took to process function
VxApplication::update()
. It includes the wait time. - getFrame() - The current frame. Simulation Frame is only increased when simulation is running
- getApplicationMode() - Current mode (see Application Mode)
- getSyncMode() - The the sync mode of this node
- getNodeName() - The node name of this application, as defined in the application setup.
- isPaused() - Indicates if the application is in mode simulating and paused.
- isSimulationRunning() - Indicates if the application is in mode simulating and running.
- The Application Services
- getErrorManager() - Get the error management services. Allows to set the log level, register a callback to get errors reported by any nodes.
- getEventManager() - Get the event manager, allowing users to send and receive events. (see Events)
- getControlPresetsManager() - Get the control presets manager. This is an asynchronous API that allows creation and edition of device mapping. (see Devices)
- getProfiler() - Get the profiling services for advanced debugging. (see tutorial ExDynamicsExtension for an example)
- getMetricsManager() - Get the Metrics Manager.
- getKeyFrameManager() - Get the Key frame manager. (see Key Frames)
- getKinematicRecorder() - Get the kinematic recording facilities. (see Record and Playback)
- getSimulatorMonitor() - Get the simulator monitor. It is an API that gives you information on the simulator
- getRoleSeatManager() - Get the API to manipulate role and seats.
- getRemoteAccessManager() - Get the Remote Access settings (see Remote Debugging)
- Threading services
- getSimulationThreadID() - Return the thread Id of the simulation thread.
- isProcessingUpdate() - Indicates if the simulation thread is in the process of calling
VxApplication::update()
. - 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.
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.
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.
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.
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
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)
Successful output
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.
... 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.