Vortex Studio SDK: Creating Content

Topics

Content Creation

The main tool for content creation is the Vortex® Studio Editor. The Editor manages all the dependencies between the documents. For example, it ensures that a change in a mechanism document is reflected in the scene where that mechanism is being used.

However, a user could want to automatically create content in code (in C++ or Python) but some key concepts must be understood for that content to be compatible with Vortex Studio. The reader is expected to understand extensions, smart interfaces and fields, described in Customizing Vortex.

Using an Application

Vortex Studio SDK internally uses its own concepts. Content classes are IObjects. While some content classes can be created and manipulated without a Vortex application, many features (e.g., configuration) will not work without a VxApplication. The Vortex Studio Editor is a VxApplication, working in Editing mode when content is being created. It is recommended to create or edit content using a VxApplication. Objects being edited should be added to the application before editing them.

Definitions and Instances

The Vortex Studio Editor allows you to create five types of content documents: Part, Assembly, Graphics Gallery, Mechanism and Scene. When a user creates a part (or any other content object), it creates a definition. When a user inserts that part into an assembly, it uses a special copy of that definition, called an instance.

Note
Application Setups (i.e, class ApplicationConfig) are also definitions, but they are not content and work slightly differently. Setups are not covered in this section.

A definition consists of a main IObject (the parent) and some associated instances of IObject and IExtension (the children) saved into a file. An instance is a copy of a definition that remembers the definition it came from. To put a definition object into another, it must be instantiated.

Since an instance remembers its definition, when that definition changes, the instance can receive the changes via the synchronization operation called sync.

The sync operation only brings the definition data to the instance. Definition data are the parameters of an object, as well as all the data of its children. The presence and order of the children are also definition data.

In addition to definition data, there is also instance data. Instance data are the inputs/outputs distinct in each instance and are saved within the parent's definition.

There are two exceptions to this rule:

  1. The inputs and outputs values of a VHL interface extension, which can be a child of a mechanism or a scene, are instance data.
  2. The field inputActivate of a Configuration is instance data. A configuration in a mechanism is usually activated at the scene level.

The editor handles definitions, instances and sync properly without manual intervention. For example, to get the changes in the number of collision geometry of a part in a scene, the part instances of the assembly definitions using it must be synced, then the assembly instances must be synced and so on up to the scene. It is a manual process.

The Vortex Studio Editor has five types of definitions: part, assembly, Graphic Gallery, mechanism and scene, corresponding to the documents it can create. While the concept of a definition can be generalized, the editor would not know what to do with custom definitions.

A simulator should not need to perform those operations; they are editing concepts.

Definitions and Instances: Example

Adding a part to a assembly:

  • The assembly is a definition (saved in a .vxassembly file). An inserted part in that assembly is an instance of a part definition (that was saved in a .vxpart file).
  • Within the assembly document, the part instance can be positioned using the inputLocalTransform field. The field is an input, that field's instance data belongs to the definition data of the assembly.
  • If the part is opened in its own document in the editor, the definition data is being edited, such as parameters like mass, and its children, the collision geometries.
  • Any changes to the part's parameters and children (the collision geometries) will be synchronized to the assembly when going back to the assembly document in the editor. However, the position of the part instance will remain the same.
  • Going up to the mechanism and scene levels works in an analogous way.

Working with Definitions and Instances

Objects can be saved with the VxObjectSerializer. Only definitions can be saved to a file.

The serializer can be used to load objects as well. Object loaded are thus definitions. When a definition containing instances is loaded, all the definitions of the instance will be loaded as well.

All the instances are automatically synchronized after a load.

VxObjectSerializer
// Create a part
auto partDefinition = VxSmartInterface<Part>::create();

// Edit the part
partDefinition->parameterMassPropertiesContainer.mass = heavy;
partDefinition->addCollisionGeometry(sphere);

// Saving the definition
VxObjectSerializer objSerializer(partDef);
objSerializer.save("MyPart.vxpart");
Warning
VxSimulationFileManager returns instances. When loading an object, the VxSimulationFileManager loads the definition, instantiates it and returns that instance. For content creation, it is recommended to use a VxObjectSerializer, since you will need to work with definitions. VxObjectSerializer does NOT distribute content in a distributed application.

Cloning

Cloning is an operation to copy an object. A clone is independent of its source. Cloning a definition makes a definition. Cloning an instance makes an instance, linked to the same definition.

To clone, call the clone() function, available in namespace VxSim by including the file VxObjectHelpers.h.

Clone
#include <VxSim/VxObjectHelpers.h>
...
// Clone the part since I need a part with a sphere CG, but lighter
auto partDefClone = VxSim::clone(partDef);
partDefinition->parameterMassPropertiesContainer.mass = lighter;

Instantiating

Instantiating creates a copy of a definition, the resulting object is an instance but a link is kept to its definition. You cannot create an instance of an object that is not a definition. As mentioned before, instances cannot be saved on their own into a file, but instance data are saved with their parent definition.

To instantiate, call the instantiate() function, available in namespace VxSim by including file VxObjectHelpers.h.

Instantiate
#include <VxSim/VxObjectHelpers.h>
...
// Load a definition
VxObjectSerializer objSerializer();
objSerializer.load("MyPart.vxpart");

VxSmartInterface<Part> partDefinition = objSerializer.getObject();
// Instantiate a definition
VxSmartInterface<Part> partInstance = VxSim::instantiate(partDefinition);
assembly->addPart(partInstance);
// move the instance
partInstance->setLocalTransform(transform); //Instance data
partInstance->inputControlType = Part::kControlDynamic; //Instance Data

Synchronization

The synchronize operation consists of updating an instance's definition data to match its definition while keeping the instance data. Once done, the children of the object are fully updated to match the children of the definition object.

To synchronize, call the sync() function, available in namespace VxSim by including file VxObjectHelpers.h.

Sync
#include <VxSim/VxObjectHelpers.h>
...
// Modify partDef by changing the mass and adding a CG in place of a sphere
partDefinition->parameterMassPropertiesContainer.mass = heavier; // Definition data
partDefinition->inputControlType = Part::kControlStatic; //This is instance data, it won't sync to the already existing part instance, but any new part instance of this definition will have this value
partDefinition->removeCollisionGeometry(sphere);
partDefinition->addCollisionGeometry(box);
...
// Sync to get the definition data
VxSim::sync(partInstance);
// After the sync, part instance mass is heavier, have a sphere in its CG, but it`s control type and position will remain as they were set in the instantiate example

Creating Content in C++

All Vortex objects are extensions. Extensions are plugins and as such an object can be created using the extension factory with the proper factory key. However, the recommended way of using Vortex Object is by using VxSmartInterface. If the factory key is part of the desired object (as member kFactoryKey), the create() function should be used.

Creating objects
// The following code creates an object of type MyExtension
// 1) Using the extension factory
VxSmartInterface<MyExtension> myExt = VxExtensionFactory::create(MyExtensionFactoryKey);

// 2) Using the VxSmartInterface API and factory key
auto myExt = VxSmartInterface<MyExtension>::create(MyExtensionFactoryKey);
// 3) Using the VxSmartInterface API, internally uses MyExtension::kFactoryKey.
auto myExt = VxSmartInterface<MyExtension>::create();

Dynamics Objects

Most content objects are dynamics. These are your mechanisms, assemblies, constraints, attachment, attachment points, parts, and collision geometries.

These dynamics objects are represented by interfaces in the code (e.g., part, box), while the implementation is actually a dynamics extension provided by the dynamics plugin (VxDynamics.vxp).

A header file describes the interface that should be used by the developer (e.g., VxDynamics/Part.h).

The header file gives access to everything the user needs to make it work: the factory key and fields to each input, output and parameter. Some utility functions are also provided.

An ICD header file describes the name of each input, output and parameter should a user needs to access them in a generic fashion from a VxExtension.

Dynamics objects part, assembly and mechanism can be loaded and saved to disk.

Moving Dynamics Objects

Every dynamics object that can be moved implements the IMobile Interface. The position of an IMobile is set by the input inputLocalTransform and the actual position of an object is given by the output outputWorldTransform.

To move an object, rather than setting the value of the inputLocalTransform field, is is recommended to use function setLocalTransform().

Collision Geometries

The following code is an example of using a box collision geometry extension.

Collision geometry
#include <VxDynamics/Box.h>
using namespace VxDynamics;

// create a box
auto box = VxSmartInterface<Box>::create();
// Set the box dimensions (x,y,z) to (1 meters, 2 meters, 3 meters)
box->parameterDimension = VxVector3(1.0,2.0,3.0);

Parts

The following code is an example of creating a part.

Parts
#include <VxDynamics/Part.h>
using namespace VxDynamics;

// create a part
auto part = VxSmartInterface<Part>::create();

// set the mass
part->parameterMassPropertiesContainer.mass = 1.0

// Adding a CG
part->addCollisionGeometry(box);

Assemblies

The following code is an example of creating an assembly.

Assembly
#include <VxDynamics/Assembly.h>
using namespace VxDynamics;

// create an assembly
auto assembly = VxSmartInterface<Assembly>::create();

// create an instance from the part definition
VxSmartInterface<Part> partInstance = VxSim::instantiate(part);

// Position and Control type
partInstance->setLocalTransform( VxMath::Transformation::createTranslation(1.0,2.0,3.0) );
partInstance->inputControlType = VxPart::kControlDynamic;

// Add the part instance to the assembly
assembly->addPart(partInstance);

Constraints

The following code is an example of creating a hinge constraint with minimal inputs.

Hinge constraint
#include <VxDynamics/Hinge.h>
using namespace VxDynamics;

// create a hinge
auto hinge = VxSmartInterface<Hinge>::create();

// set the attachment parts
hinge->inputAttachment1.part = part1;
hinge->inputAttachment1.part = part2;

// set the attachment positions
hinge->inputAttachment1.position = VxVector3(1.0,0.0,0.0);
hinge->inputAttachment2.position = VxVector3(1.0,0.0,0.0);

// set the attachment primary axis
VxVector3 primaryAxis = VxTransform(hinge->inputAttachment1.part->inputLocalTransform.getValue()).axis(0)
hinge->inputAttachment1.primaryAxis = primaryAxis;
hinge->inputAttachment2.primaryAxis = primaryAxis;

// Add the hinge to the assembly
assembly->addConstraint(hinge);

Attachments

The following code is an example of creating an attachment with two attachment points, each referencing a different part. The example attaches part together by adding an attachment to the assembly.

Attachments can be added to a mechanism or a scene to attach assemblies or mechanisms, respectively.

Attachments
#include <VxDynamics/Attachment.h>
#include <VxDynamics/AttachmentPoint.h>

using namespace VxDynamics;

// create a first attachment point
auto attPt1 = VxSmartInterface<AttachmentPoint>::create();
attPt1->parameterParentPart= partInstance1;
assembly->addAttachmentPoint(attPt1);

// create a second attachment point and add it
auto attPt2 = VxSmartInterface<AttachmentPoint>::create();
attPt2->parameterParentPart= partInstance2;
assembly->addAttachmentPoint(attPt2);

// create an attachment using both attachment points
auto attachment = VxSmartInterface<Attachment>::create();
attachment->setAttachmentPoints(attPt1, attPt2);

// add the attachment to the assembly
assembly->addAttachment(attachment);

// attach
attachment->attach();

Mechanisms

The following code is an example of creating a mechanism.

Mechanism
#include <VxDynamics/Mechanism.h>
using namespace VxDynamics;

// create a mechanism
auto mechanism = VxSmartInterface<Mechanism>::create();

// create an instance from the assembly definition
VxSmartInterface<Assembly> assemblyInstance(VxSim::instantiate(assembly));

// Position
assemblyInstance->setLocalTransform(VxMath::Transformation::createTranslation(1.0,2.0,3.0))

// Add the assembly instance to the mechanism
mechanism->addAssembly(assemblyInstance);

Other Content Extensions

Scenes

Scenes are where you put all your mechanisms for your simulation.

Scene
#include <VxContent/Scene.h>
using namespace VxDynamics;
using namespace VxContent;

// create a scene
VxSmartInterface<Scene> scene = VxSmartInterface<Scene>::create();

// create an instance from the mechanism definition
VxSmartInterface<Mechanism> mechanismInstance(VxSim::instantiate(mechanism));

// Position
mechanismInstance->setLocalTransform( VxMath::Transformation::createTranslation(1.0,2.0,3.0) )

// Add the mechanism instance to the scene
scene->addMechanism(mechanismInstance);

VHL Interface

VHL Interfaces are extensions that give direct access to its children's fields by mapping it to a VHL field. VHL Interface can be added to a mechanism or a scene.

Adding inputs, outputs or parameters is available through the interface.

VHL Interface in mechanism
// Create the VHL extension
VxSmartInterface<VxVHLInterface> vhlInterface = VxSmartInterface<VxVHLInterface>::create();

// Add a hinge control field as input of VHL
Vx::VxID kInputControl("Input Value");
vhlInterface->addInput(kInputControl, &hinge->inputAngularCoordinate.control);

// Add the VHL extension to the mechanism
mechanism->addExtension(vhlInterface);

Connection Container

Connection containers are extensions that contain connections between fields. Fields that can be connected must be children of the connection container's parent, typically a mechanism.

Adding connections is available through the interface.

Connection container
// Create the connection container extension
auto connectionContainer = VxSmartInterface<ConnectionContainerExtension>::create();

// Add a connection between two fields : my custom extension output and a part input control
// Types have to be compatible for the connection creation to succeed
Vx::VxID kMyCustomOutput("My Custom Output");
int connectionIndex = connectionContainer->create(myCustomExt->getProxy()->getOutput(kMyCustomOutput), &part->inputControlType);

// Add the connection container to the mechanism
mechanism->addExtension(connectionContainer);

Python Scripting Extension

Python scripting extensions are standard extensions to control an aspect of the simulation in code. These are handled by the dynamics module and are added to a mechanism or a scene.

The script file parameter refers to the .py file to execute. It is also possible to set the Python code to execute as a string in the code parameter, but it is less convenient for editing.

The script extension can be edited to dynamically add inputs, outputs and parameters that can be used by the Python script.

The added fields can be referred by the script using self.inputs.<field added>, self.outputs .<field added>, self.parameters .<field added>, where <field added> is the name of the field with invalid characters replaced by underscore. The value is available from the value property of the Python field.

Python extensions do not have a dedicated interface; VxSmartInterface of IExtension should be used.

Python scripting extension
// Create the Python scripting extension
VxSmartInterface<IExtension> pythonExt = VxSim::VxExtensionFactory::create(VxSimPython::DynamicsICD::kFactoryKey);

// Add the scripting file as parameter - the script can also be added as a string
pythonExt->getProxy()->getParameter(VxSimPython::DynamicsICD::kScriptFile)->setValue(std::string("/mypythonscript.py"));

// Add a parameter of type Part and set its reference
Vx::VxID kParamPart("Part");
pythonExt->getProxy()->addParameter(kParamPart, VxData::Types::Type_Part);
pythonExt->getProxy()->getParameter(kParamPart)->setValue(part.getExtension()); // "Part" extension can be read in the script using self.parameters.Part.value

// Add an input of type boolean and set its value
Vx::VxID kInputSwitch("Switch");
pythonExt->getProxy()->addInput(kInputSwitch, VxData::Types::Type_Bool);
pythonExt->getProxy()->getInput(kInputSwitch)->setValue(false); // "Switch" boolean can be read in the script using self.inputs.Switch.value

// Add an output of type integer and set its value
Vx::VxID kOutputControl("Control Mode");
pythonExt->getProxy()->addOutput(kOutputControl, VxData::Types::Type_Int);
pythonExt->getProxy()->getOutput(kOutputControl)->setValue(0); // "Control Mode" int can be written in the script using self.outputs.Control_Mode.value

// Add the connection container to the mechanism
mechanism->addExtension(pythonExt);

Configuration

Configurations are extensions used by mechanisms and scenes to configure them. By this, we mean that a configuration is a set of modifications to the content that can be applied or not. The main goal is to make the mechanism or scene more reusable. Rather than having multiple copies of the object with just a small number of differences, configuration allow you to keep one base object and as many distinct configurations as you want.

Configurations are set when the application is in editing mode, before the simulation is run. They cannot be changed in simulation mode.

Basics
  • When added to an object (a mechanism or a scene), VxContent::Configuration can reference other extensions in the same object, allowing you to modify inputs and parameters of referenced extensions or remove them from content.
  • A configuration does not create extensions; the extensions must be in the object without configuration.
  • An object can have multiples configurations.
  • A configuration is only a set of modifications to be applied to one or many referenced extensions. The type of modifications are called actions.
  • The actions are applied on the referenced extensions when activated.
  • Multiple configurations can be activated at the same time, but a configuration cannot be activated if there is a conflict between changes.
  • Activation can only happen while the application is in editing mode.
Configuration Actions

A configuration can do one of the following actions, given by enum eConfigurationAction, on a given referenced extension:

  • Modify (kModify) :
    • When configuration is activated, selected inputs/parameters values are modified in the referenced extension; the referenced extension will be sent to the modules normally.
    • When configuration is not activated, all inputs/parameters values are untouched in the referenced extension; the referenced extension will be sent to the modules normally.
    • This is the default action.
  • Remove (kRemoveOnActivation) :
    • When configuration is activated, the referenced extension will not be sent to the modules.
    • When configuration is not activated, the referenced extension will be sent to the modules normally; all inputs/parameters values are untouched in the referenced extension.
  • Add (kAddOnActivation):
    • When configuration is activated, the referenced extension will be sent to the modules normally; selected inputs/parameters values are modified in the referenced extension.
    • When configuration is not activated, the referenced extension will not be sent to the modules.
Configuration Conflicts

Two configurations will be in conflict if:

  • They are modifying values in the same extensions.
  • One configuration modifies values and the other wants to remove the extension.
  • One configuration wants to add an extension, and the other wants to modify or remove the same extension.
How It Works

The configuration extensions are handled by the Configuration Module. The module is part of a VxApplication and does not need to be added by the application setup.

The activation of a configuration is not instantaneous, it requires an update so that it can be treated by the Configuration Module and propagated properly across the network.

The field inputActivate is instance data, activating a configuration in a mechanism definition won't affect which configurations are activated in the scene. An activated configuration in the definition only affects which configurations will be activated when the instance is created.

A configuration in a scene can refer to a configuration in a mechanism instance, allowing you to use the configuration of a scene to configure a mechanism.

Activating or editing a configuration must be done in editing mode. Adding references should be done when the extension is not activated. In order to have a different set of inputs or parameters values for a reference extension, it must be added to a configuration (with action kModify or kAddOnActivation), then the configuration must be activated. When a user edits the values of a referenced extension, the changed values will only be in effect when the configuration is activated. The code sample below shows an example.

Using the Configuration API

The Configuration API offers several functions to ease edition.

  • Function addReference() allows you to reference an extension in a configuration, with one of the following actions: kModify, kRemoveOnActivation or kAddOnActivation.
    • Function will return a ConfigurationErrors container. If it is not empty, it is possible to inspect each ConfigurationErrors to find out the problem.
    • Function canAddReference() will help you figure out if you can add a reference to an extension in a configuration.
  • Function removeReference() allows you to remove a reference to an extension in a configuration. The returned object can be kept to revert the removed extension with all of its modifications.
  • To activate the configuration, field inputActivate should be set to true. It requires an update to be propagated.
    • If there is a conflict, configuration will not be activated but will stay in that state. If the conflict gets resolved, it will activate on its own.
  • Use field outputActivated to determine activation success.
  • Use function getRuntimeErrors() to find a conflict in activation.
  • Function canActivate() should be called before setting inputActivate to true, to know what will be the result of the activation.
  • Function restoreReference() allows you to restore a removed extension with its set of modifications.
  • Function getReferencedExtensions() allows you to consult the referenced extensions with their associated actions within a configuration.
Configuring a mechanism
...
// Add 4 extensions to mechanism
myExtension1 = VxExtensionFactory::create(MyExtension::kFactoryKey);
myExtension2 = VxExtensionFactory::create(MyExtension::kFactoryKey);
myExtension3 = VxExtensionFactory::create(MyExtension::kFactoryKey);
myExtension4 = VxExtensionFactory::create(MyExtension::kFactoryKey);

// Sets default value
myExtension1->parameterA = 1;
myExtension1->parameterB = 2;
myExtension1->parameterC = 3;

myExtension2->parameterA = 4;
myExtension2->parameterB = 5;
myExtension2->parameterC = 6;

myExtension3->parameterA = 7;
myExtension3->parameterB = 8;
myExtension3->parameterC = 9;

myExtension4->parameterA = 10;
myExtension4->parameterB = 11;
myExtension4->parameterC = 12;

mMechanism->addExtension(myExtension1);
mMechanism->addExtension(myExtension2);
mMechanism->addExtension(myExtension3);
mMechanism->addExtension(myExtension4);
...
// Create a configuration
mConfiguration = VxSmartInterface<Configuration>::create();
mConfiguration ->setName("Configuration1");
mMechanism->addExtension(mConfiguration );

// Configuration will modify Extension 1, remove extension 2 and add extension 4
mConfiguration->addReference(myExtension1, VxContent::kModify);
mConfiguration->addReference(myExtension2, VxContent::kRemoveOnActivation);
mConfiguration->addReference(myExtension4, VxContent::kAddOnActivation); // Because the flag is AddOnActivation, myExtension4 is immediately removed from the content. It still exist with mMechanism but no module will received it.

// Start editing
if(mConfiguration->canActivate()) // just to make sure there is no conflict
{
mConfiguration->inputActivate = true; // This removes myExtension2 and adds myExtension4.
mApplication->update(); // requires an application update

if(mConfiguration->outputActivated.getValue())
{
// Edit the extension 1 and 4
myExtension1->parameterA = -1;
myExtension1->parameterC = -10;

myExtension4->parameterB = 42;

// Done editing
mConfiguration->inputActivate = false;
mApplication->update(); // requires an application update
}
else
{
// Handles errors
auto& errors = mConfiguration->getRuntimeErrors();
}
}

... continue edition, create a scene
VxSmartInterface<Scene> scene = VxSmartInterface<Scene>::create();
//instantiate a mechanism
mMechInstance = VxSim::Instantiate(mMechanism);

// myExtension1 is in the content, parameterA value is 1, parameterB is 2 and parameterC is 3
// myExtension2 is in the content, parameterA value is 4, parameterB is 5 and parameterC is 6
// myExtension3 is in the content, parameterA value is 7, parameterB is 8 and parameterC is 9
// myExtension4 is NOT in the content
mScene->addMechanism(mMechInstance);

// Activate the mechanism configuration in the scene
VxSim::VxSmartInterface<Configuration> confInstance = mMechInstance.getObject()->findExtensionByName("Configuration1");
confInstance->inputActivate = true;
mApplication->update(); // requires an application update

// myExtension1 is in the content, parameterA value is 11, parameterB is 2 and parameterC is -10
// myExtension2 is NOT in the content
// myExtension3 is in the content, parameterA value is 7, parameterB is 8 and parameterC is 9
// myExtension4 is in the content, parameterA value is 10, parameterB is 42 and parameterC is 12

Graphics Objects

Creation of graphics objects (including terrain) in code is a complex subject out of the scope of this guide. Please use the Vortex Studio Editor to manipulate Graphics Galleries and its sub-objects.

Content Creation in Python

Content can be created in Python as well. See Python Scripting for more information.

Content Creation Tutorials

ExContentCreation Tutorial

The purpose of this tutorial is to show how to create a rover sample loadable in the Editor and the Player, using C++ code. The rover has no graphics but it shows the features described above. This tutorial is found in your Vortex Studio installation folder.

 

Next topic: Python Scripting