Python 3 Scripting

This section describes how to extend and add additional functionality by using the built in scripting tool.

Supported Use Case

The Dynamics Script Extension (Python 3) allows you to use the Python 3 programming language to modify the behaviour of dynamic elements of your simulation while it is running.

For example, if a mechanism consists of two parts that are constrained by a hinge, adding tension to that mechanism will not cause the hinge to break. However, a simple script that reads the tension and disables the constraint if it reaches a certain threshold will accurately simulate breaking the hinge.

A Dynamics Script is part of your content. The extension can be added to either a scene, mechanism or assembly document.

Basic Concepts

Python Interpreter Support

The Dynamics Script extension makes it easy to work with a Python 3 interpreter installed on your system. Vortex Studio comes bundled with a default interpreter but if you want to use third party packages or a different Python distribution you should point your Vortex application
towards an external interpreter that you previously installed.

The only restriction is that the supported Python version is Python 3.8. The default interpreter currently bundled with Vortex Studio is 3.8.6.


Workflow

  1. (Optional) Setting the Python Interpreter
  2. Adding a Dynamics Script extension to your content
  3. Adding Fields
  4. Writing a Dynamics Script's Code


Setting the Python Interpreter

Refer to the following documentation for additional information on a Vortex application's setup file and how to modify it.

Vortex provides a default Python Interpreter. If you need your own, you can specify it at the root level of a setup document, under the container Python 3.

See Python 3 Scripting - Python Usage and Environment Configuration


Adding a Dynamics Script extension to your content

Prerequisites: If you need to use another Python 3 interpreter than the one bundled with Vortex Studio, follow the steps labelled Setting the Python Interpreter above.

  1. Launch the Vortex Studio Editor.
  2. Open your content file or create a new assembly, mechanism or scene.
  3. In the Toolbox panel, under the Scripting category, add a Dynamics Script extension.
  4. In the Properties panel, set the path to the script you want to use to the parameter Script Path.

Your script will be automatically analyzed and any syntax error will be reported to you as warnings on the extension.
Every time, you edit and save your script in your code editor, it will be reimported into Vortex.

Adding Fields

The most direct way to communicate between Vortex and your Python script is to use fields (inputs, outputs and parameters) on the Dynamics Script extension. Both the Vortex application and the Python script can access those fields.
But before accessing fields you must first add them to the extension.

Using the Vortex Studio Editor

Right click on the dynamics script extension and select "Edit"

In the window that appears you can add inputs, outputs and parameters to your script.


There are 3 field containers on each scripts,

  • Inputs
  • Outputs
  • Parameters

in Python access the fields using the syntax

"extension.inputs.field_name"

"extension.outputs.field_name"

"extension.parameters.field_name"

See code below as an example accessing fields created earlier.

Accessing fields in Python 3
def pre_step(extension):
    # The boolean input field in the screenshot above is named "boolean input"
    # Notice that spaces are replaced by underscores
    my_field = extension.inputs.boolean_input

    # my_field is a boolean input, you can read its value like so
    bool_value_of_my_field = my_field.value
    if bool_value_of_my_field:
        extension.outputs.an_output_double.value = 3.1415
    else:
        parameter_number = extension.parameters.number.value
        extension.outputs.an_output_double.value = parameter_number

Using Python Code only

Fields can be created directly from Python code if you prefer.  The following code is functionally the same as the previous example.

Creating fields from a script in python 3
import Vortex

def on_simulation_start(extension):
    # Creating an input of type boolean
    extension.createInput("boolean input", Vortex.Types.Type_Bool)
    # Creating an output of type double
    extension.createOutput("an output double", Vortex.Types.Type_Double)
    # Creating a parameter of type double
    extension.createParameter("number", Vortex.Types.Type_Double)

def pre_step(extension):
    # The boolean input field we created on simulation start is named "boolean input"
    # Notice that spaces are replaced by underscores
    my_field = extension.inputs.boolean_input

    # my_field is a boolean input, you can read its value like so
    bool_value_of_my_field = my_field.value
    if bool_value_of_my_field:
        extension.outputs.an_output_double.value = 3.14159265359
    else:
        parameter_number = extension.parameters.number.value
        extension.outputs.an_output_double.value = parameter_number


More information on creating and getting fields in python

The functions createInput(), createOutput() and createParameter() are utility functions each encapsulating a frequent use case of this API. There is several things to mention about these functions.

  • The new functions have the following signatures

    create field functions signatures
    def createInput(name, type, defaultValue = None, description = None, physicalDimension = None)
    def createParameter(name, type, defaultValue = None, description = None, physicalDimension = None)
    def createOutput(name, type, defaultValue = None, description = None, physicalDimension = None)
    
  • The available types for the parameter type are in Vortex.Types enumeration. If this parameter is an invalid value, this function will fail. 
  • The available types for the parameter physicalDimension are in Vortex.VxPhysicalDimension enumeration. By default, Vortex will assign a value corresponding to VxSim.VxPhysicalDimension.kNone to fields whose physical dimension are not defined. This means the function getPhysicalDimension() will never return None even if explicitly declared in the call to createInput(), createOutput() or createParameter(). This behavior also is consistent with the addInput(), addParameter(), addOutput() functions.
  • The functions createInput() and createParameter() ignore the parameter defaultValue if the field already exists. This allows experimenting with different values in the editor's interface without having to change the script.
  • The function createOutput() reset the output field's value to defaultValue even if the field already exists. This behavior is different from createInput() and createParameter().

Each function follows the pseudo code presented in the following code snippet.

create field functions pseudo-code
def createInput(name, type, defaultValue = None, description = None, physicalDimension = None) :
    # 1. ASSIGN $field a reference to an existing input field of name $name. 
    # 2. IF $field is NONE: ASSIGN $field a new input field named $name of type $type; ELSE CONTINUE to #4;  
    # 3. IF $defaultValue is not NONE: ASSIGN the new $field's value the value of $defaultValue
    # 4. IF $description is not NONE: ASSIGN the $field's description the value of $description
    # 5. IF $physicalDimension is not NONE: ASSIGN the $field's physicalDimension the value of $physicalDimension
    # 6. RETURN $field

def createParameter(name, type, defaultValue = None, description = None, physicalDimension = None) :
    # 1. ASSIGN $field a reference to an existing parameter field of name $name. 
    # 2. IF $field is NONE: ASSIGN $field a new parameter field named $name of type $type; ELSE CONTINUE to #4; 
    # 3. IF $defaultValue is not NONE: ASSIGN the new $field's value the value of $defaultValue
    # 4. IF $description is not NONE: ASSIGN the $field's description the value of $description
    # 5. IF $physicalDimension is not NONE: ASSIGN the $field's physicalDimension the value of $physicalDimension
    # 6. RETURN $field

def createOutput(name, type, defaultValue = None, description = None, physicalDimension = None) :
    # 1. ASSIGN $field a reference to an existing output field of name $name. 
    # 2. IF $field is NONE : ASSIGN $field a new output field named $name of type $type; ELSE CONTINUE to #3; 
    # 3. IF $defaultValue is not NONE: ASSIGN the $field's value the value of $defaultValue
    # 4. IF $description is not NONE: ASSIGN the $field's description the value of $description
    # 5. IF $physicalDimension is not NONE: ASSIGN the $field's physicalDimension the value of $physicalDimension
    # 6. RETURN $field

Writing a Dynamics Script's Code

The Dynamics Script extension supports the Python 3 programming language and gives the user access to all the rich features of this language.

Available Callbacks

FunctionDescriptionParameters
on_simulation_start(extension)Called when the application mode changes from editing to simulating.extension: The DynamicsScript extension referring to this script.
on_simulation_stop(extension)Called when the application mode changes from simulating to editing.extension: The DynamicsScript extension referring to this script.
pre_step(extension)Equivalent to IDynamics::preStep. Called before the collision detection and the dynamic solver.extension: The DynamicsScript extension referring to this script.
post_step(extension)

Equivalent to IDynamics::postStep. Called after the collision detection and after the dynamic solver.

extension: The DynamicsScript extension referring to this script.
paused_update(extension)Equivalent to IDynamics::pausedUpdate. Called at every update in editing mode and when the
simulation is paused.
extension: The DynamicsScript extension referring to this script.
on_keyframe_save(extension, data)Equivalent to IExtension::onStateSave.

extension: The DynamicsScript extension referring to this script.

data: A dictionary object in which you can save additional arbitrary data.

on_keyframe_restore(extension, data)Equivalent to IExtension::onStateRestore.

extension: The DynamicsScript extension referring to this script.

data: A dictionary object from which you can restore the additional arbitrary data
saved in the on_keyframe_save function.

Code template

Below is a sample code that can be used as a template for any dynamics script extension.

Template
import Vortex

"""Please removed any function that are not needed.
It is more efficient as the simulation will not invoke a non-existing function rather then using Python to invoke an empty function.

"""

def on_simulation_start(extension):
    """ Called when the application mode changes from editing to simulating.
    Use this method to define specific actions that must be taken at the start of the simulation.
    
    Parameters
    ----------
    extension : object 
        The DynamicsScript extension referring to this script.
    
    """
    pass

def on_simulation_stop(extension):
    """ Called when the application mode changes from simulating to editing.
    Use this method to define specific actions that must be taken at the end of the simulation.
    
    Parameters
    ----------
    extension : object 
        The DynamicsScript extension referring to this script.
    
    """
    pass

def pre_step(extension):
    """ Called before the collision detection and before the dynamic solver.
    Use this method to get inputs or set values to dynamics objects.
    
    Parameters
    ----------
    extension : object 
        The DynamicsScript extension referring to this script.
    
    """
    pass

def paused_update(extension):
    """ Called at every update in editing mode and when the simulation is paused.
    
    Parameters
    ----------
    extension : object 
        The DynamicsScript extension referring to this script.
    
    """
    
    pass
    
def post_step(extension):
    """ Called after the collision detection and after the dynamic solver.
    Use this method to set outputs or get values from dynamics objects.
    
    Parameters
    ----------
    extension : object 
        The DynamicsScript extension referring to this script.
    
    """
    
    pass


def on_keyframe_save(extension, data):
    """ Called after a keyframe has been taken.
    Use the data parameter to store values that will be provided back in the on_keyframe_restore
    These values can be anything used in that script that you want restore to this point in time.
    The extension itself will be properly restored and thus it's data do not need to be saved manually.
    
    Parameters
    ----------
    extension : object 
        The DynamicsScript extension referring to this script.
    
    data : dictionnary
        Store values that will be provided back in the on_keyframe_restore
        
        The following types are supported: booleans, integers, long integers, floating point
        numbers, complex numbers, strings, Unicode objects, tuples, lists, sets, frozen sets,
        dictionaries, and code objects. 
        
        Tuples, lists, sets, frozen sets and dictionaries are only supported as long as the values contained therein are
        themselves supported; and recursive lists, sets and dictionaries should not be written (they will cause infinite loops).
        The singletons None, Ellipsis and StopIteration can also be saved and restored.
        
    """
    pass
    
def on_keyframe_restore(extension, data):
    """ Called after a keyframe has been fully restored.
    
    Use the data parameter to restore the script to the point where the data was captured.
    
    Parameters
    ----------
    extension : object 
        The DynamicsScript extension referring to this script.
    
    data : dictionnary
        Stored values capture during the last call to on_keyframe_save
    """
    pass



Available helper functions to write scripts

The Application Context

To get information relative to the application context, use getApplicationContext(). The method is available via the extension in dynamics Python scripts extension. Application context API is relatively simple and can be used for time/frame based related logic.

The Application Context
extension.getApplicationContext().getSimulationFrameRate() # Frame rate e.g., 60 means 60 fps
extension.getApplicationContext().getSimulationTimeStep() # Time of a step. Is the inverse of frame rate e.g., Frame Rate = 60, Time Step = 1/60 = 0.016666
extension.getApplicationContext().getSimulationTime() # Current Time of the simulation. Time increases by Time step every frame.
extension.getApplicationContext().getFrame() # Current Frame of the simulation
extension.getApplicationContext().getApplicationMode() # Current Simulation mode i.e., kModeEditing, kModeSimulating or kModePlayingBack
extension.getApplicationContext().isPaused() # Indicates if the simulation is paused
extension.getApplicationContext().isSimulationRunning() # Indicates if the Simulation is running i.e., ApplicationMode is kModeSimulating And isPaused == False


VxMath::Transformation

A series of global helpers exists at the VxSim level to simplify matrix computation. They are the equivalent of the C++ Global helpers in namespace VxMath::Transformation.

Transformation Helpers
# Scale, Rotation and Translation Matrix Constructor
m = Vortex.createScale(x,y,z) # Create a scale matrix.
m = Vortex.createScale(scale) # Create a scale matrix from VxVector3.
m = Vortex.createRotation(rx, ry, rz) # Creates a rotation matrix. rx, ry and rz are Euler angles given in radian, using the default Euler order
m = Vortex.createRotation(axis, angle) # Creates a rotation matrix from an axis (VxVector3) and angle (radian).
m = Vortex.createRotation(quat) # Creates a rotation matrix from a quaternion
m = Vortex.createRotationFromOuterProduct(v, w) # Creates a rotation matrix from the outer product of two 3 dimensions vectors.
m = Vortex.createTranslation(tx, ty, tz) # Creates a translation matrix.
m = Vortex.createTranslation(translation) # Creates a translation matrix from VxVector3.

# Creates a transform matrix with a position sets to the eye and oriented to the center.
# The first component of the matrix is the forward vector, the second one is the side vector and the third is the up.
m = Vortex.createObjectLookAt(eye, center, up)

# The first component of the matrix is the side-way vector, the second one is the up vector and the third is pointing backward to the center.
m = Vortex.createCameraLookAt(eye, center, up)

# Creates an orthographic projection matrix.
m = Vortex.createOrthographic(left, right, bottom, top, zNear, zFar)

# Creates a non-regular projection frustum.
m = Vortex.createFrustum(left, right, bottom, top, zNear, zFar)

# Creates a regular perspective projection frustum.
m = Vortex.createPerspective(fovy, aspectRatio, zNear, zFar)

# Extraction helpers
s = Vortex.getScale(m) # Get Scale VxVector3 from Matrix44
r = Vortex.getRotation(m) # Get Rotation VxVector3 from Matrix44
t = Vortex.getTranslation(m) # Get Translation VxVector3 from Matrix44

# Checks whether this matrix includes a perspective projection.
b = Vortex.isPerspectiveProjection(m)

# Affine Matrix operation
mt = Vortex.translateTo(m, translation) # Sets translation VxVector3 on m
mr = Vortex.rotateTo(m, rotation) # Sets rotation VxVector3 on m
ms = Vortex.scaleTo(m, scale) # Sets scale VxVector3 on m
m = Vortex.compose(scale, rotation, translation, flip) # Creates a matrix by composition with a VxVector3 scaling, then a VxVector3 rotation and a VxVector3 translation.
scale, rotation, translation, flip = Vortex.decompose(m) # Decomposes the affine matrix m into a scale, rotation and translation matrix. Rotation are given in the range [-pi, pi] for x, [-pi/2, pi/2] for y, [-pi, pi] for z.

Accessing Contact Data 

With Python 3, contacts generated during the simulation can now be accessed in a Dynamics Script Extension. By calling the function getDynamicsContacts() as shown below, the DynamicsContactCollection is returned. This structure has two useful member functions, __len__(), which returns the number of contacts, and __iter__() which implements a Python iterator on the collection. This can be used as shown below to examine various properties of each contact that has been generated.

Accessing Contacts
import Vortex

def pre_step(extension):
	dynamicsContacts = extension.getInput("part").toDynamicPart().getInterface().getDynamicsContacts()
	for contact in dynamicsContacts:
		# retrieve contact information here 

Two sets of get functions are available, the first set returns information relevant to the contact interaction, allowing examination of the position and normal direction, as well as the primary and secondary friction directions at the contact point.

Contact Data
pos = contact.getPosition() 
normal = contact.getNormal() 
primary = contact.getPrimaryDirection() 
secondary = contact.getSecondaryDirection() 

The second set returns information for each part involved in the contact interaction, allowing examination of the contact force and torque on each part. These functions take a partID as an argument, which is just an integer labeling each part involved.

Part Data
contact_force_part0 = contact.getForce(0)
contact_torque_part0 = contact.getTorque(0)

Getting the full Vortex.py documentation

If you wish to see more of the Vortex API you can generate the pydoc. Simply open a command-line tool in the bin folder of your vortex installation and run the following command:

python -m pydoc -w Vortex Vortex.html

The file Vortex.html will be generated in the bin folder and you can browse an up-to-date list of all the functions available in the Vortex API.

 Using the Python Output Window

In the editor, a new window was added to help you see what is happening in your Python scripts

The Python Output panel is a text box where the output of all of your scripts is written. Any print statement in your script will print in this window and any error in execution will also show.

Make sure that you turn on the listening mode:

Now if you introduce an error in your code

If you want to know which script is the culprit for the error, you can click the error message and then the "bulls-eye"  to locate the extension with the error in the explorer tree or click the open source script icon to open the text file directly.

You can also use the print statement:

def on_simulation_start(extension):
    print("A very helpful message")



Example: Writing a simple script to automatically change the gears of a car

For convenience, this example starts with the sample file that you can find in this location: C:\CM Labs\Vortex Studio Content <version>\Samples\Vehicles\Car - Sedan\Car - Sedan.vxmechanism

Add a Dynamics Script to the mechanism

  1. Next to the mechanism file, create an empty text file and name it control.py
  2. In the toolbox, locate the Dynamics Script extension and add it to the mechanism 

  3. In the properties panel, set the Script Path to the empty script you created at step 1. 
  4. Now to add inputs and outputs to the script extension. Find the extension in the explorer tab, right-click it → edit
    1. Add an input
      1. rpm - double
    2. Add 3 outputs,
      1. engine - boolean
      2. throttle - double
      3. gear - int
    3. Add a parameter
      1. redline - double
  5. You should have something like this:
  6. Press Ok to confirm your fields


Now, your script extension should appear as follow:

Writing the code to start the car

  1. Open the file control.py, that you created, with any code editor of your choice
  2. Start by adding code that will be run when we start the simulation 

    1. control.py
      # First we import Vortex
      import Vortex
      
      def on_simulation_start(extension):
          # Turn on the engine by default
          extension.outputs.engine.value = True
          # Set the throttle to 1.0 at the start, 1 represents the pedal pressed all the way in, we want to go as fast as possible
          extension.outputs.throttle.value = 1.0
          # Switch to first gear right at the start
          extension.outputs.gear.value = 1
          
  3. Run the simulation, see the output fields values change

  4. Nothing happens to the car!
  5. We have to connect the outputs of the script to the vehicle interface
    1. Add a connection container
    2. connect all the fields of the dynamics script to the corresponding fields of the vehicle interface

Run the simulation again, the car moves and starts driving forward but it doesn't change gear, see the rpm maxed out at 6500:

Changing gears at runtime whenever we hit the rpm redline

  1. Set the dynamics script redline parameter value to 6000
  2. We will add the pre_step method, this callback is called every before every physics update of the simulation

    control.py
    ...
    def pre_step(extension):
        # Check if we hit our redline
        if extension.inputs.rpm.value >= extension.parameters.redline.value:
            # Gear up!
            extension.outputs.gear.value += 1
  3.  Press play and look at what happens

    1. We gear up a lot as soon as we hit 6000rpm, the pre_step method is called 60 times per second by default, sometimes its too quick for our needs. Something needs to be corrected so we don't shift up more than once before the rpm drops.


  4. We want to add two things, first our car has only 6 gears, we should never gear up above this, second, we want to add a minimum time delay between each gear change

    control.py
    def on_simulation_start(extension):
        ...
        ## We can store variables in the extension object for use later on
        # Gear up at maximum once per second
        extension.gear_up_time_delay = 1.0
        # Store a timestamp of the last gear change
        extension.last_gear_change = extension.getApplicationContext().getSimulationTime()
    
    def pre_step(extension):
        # Check that we have not already hit max gear
        if extension.outputs.gear.value < 6 and extension.inputs.rpm.value >= extension.parameters.redline.value:
    
            simulation_time = extension.getApplicationContext().getSimulationTime()
            # Check if it makes more than the gear_up_time_delay from the last time we did a gear change
            if simulation_time - extension.last_gear_change > extension.gear_up_time_delay:
                extension.last_gear_change = simulation_time
    
                # Gear up!
                extension.outputs.gear.value += 1
  5. The final script should look like this:

    control.py
    # First we import Vortex
    import Vortex
    
    def on_simulation_start(extension):
        # Turn on the engine by default
        extension.outputs.engine.value = True
        # Set the throttle to 1.0 at the start, 1 represents the pedal pressed all the way in, we want to go as fast as possible
        extension.outputs.throttle.value = 1.0
        # Switch to first gear right at the start
        extension.outputs.gear.value = 1
        
        ## We can store variables in the extension object for use later on
        # Gear up at maximum once per second
        extension.gear_up_time_delay = 1.0
        # Store a timestamp of the last gear change
        extension.last_gear_change = extension.getApplicationContext().getSimulationTime()
    
    # This will be executed repeatedly every step of the simulation
    def pre_step(extension):
        # Check that we have not already hit max gear and we have hit our redline
        if extension.outputs.gear.value < 6 and extension.inputs.rpm.value >= extension.parameters.redline.value:
    
            simulation_time = extension.getApplicationContext().getSimulationTime()
            # Check if it makes more than the gear_up_time_delay from the last time we did a gear change
            if simulation_time - extension.last_gear_change > extension.gear_up_time_delay:
                extension.last_gear_change = simulation_time
    
                # Gear up!
                extension.outputs.gear.value += 1

Now, if you play the simulation, you will have a car that accelerates and gears up gradually, also you will find out that this sedan is very very fast easily reaching over 300km/h!


Accessing an Extension's Fields via Python

In some cases you may want to access a field, or a nested field of an extension via a Python script. An extension's inputs, outputs, and parameters can be accessed by passing their name (as a string) into one of the following functions:

  • extension.getInput('Example Name')
  • extension.getOutput('Example Name')
  • extension.getParameter('Example Name')

The value of that field can then be read or written-to by accessing it like so:

  • extension.getInput('Example Name').value

In some cases, the field that you would like to access may be nested inside of another field. Fields function as containers, and you can access fields within them via an integer-based index or a string-based key that represents the field's name.

For example, here the "Number Of Cables" field is contained within the "Cables" output:

It can be accessed via the following syntax:

control.py
the_field = extension.getOutput('Cables')['Number Of Cables']
the_field_value = extension.getOutput('Cables')['Number Of Cables'].value

Converting Python 2 scripts to Python 3 Dynamics Script

If you had existing Python 2 scripts in your content you can easily convert it to a Dynamics Script in a few steps.

This action does not convert the code itself, it only replaces the extension in the content. It ensures that the new extension has the same fields and that references are maintained. The code must be upgraded manually. There are some tips below.


Convert the extension to Dynamics Script

The first thing you will want to do is use the "Convert to Dynamics Script" action in the editor.

To do so, right-click on the desired script:

The "Convert to Dynamics Script" action will change the extension type of the Python 2 script to the Python 3 dynamics script extension.

Doing this will keep all the fields, connections, and references of the extension.


The Convert to Dynamics Script action will have created a duplicate version of your script file with the suffix _DynamicsScript or if the script was embedded there will be a new .py file in the folder next to the current document.

Converting multiples scripts

The editor API does not permit converting multiple scripts at once. It can be done using the Python API. For more information, see Python 3 API: Upgrading Vortex Files

Converting Python 2 code to Python 3

Here is an example code conversion that covers most cases for Vortex scripts, all changes are highlighted


Python 2 scriptPython 3 conversion



import VxSim
import math


def on_add_to_universe(self, universe):
    print "Initializing my script"
    self.rotation_angle = 0

def on_remove_from_universe(self, universe):
    print "Simulation Stopping"


def pre_step(self):
    self.rotation_angle += 1

def post_step(self):
    self.outputs.rotation_transform.value = (
        VxSim.createRotation(0, 0, math.radians(self.rotation_angle)))



def on_state_save(self, data):
    if hasattr(self, "rotation_angle"):
        data["rotation_angle"] = self.rotation_angle
    else:
        data["rotation_angle"] = 0

def on_state_restore(self, data):
    self.rotation_angle = data["rotation_angle"]





import Vortex
import math


def on_simulation_start(extension):
    print("Initializing my script") # requires parentheses
    extension.rotation_angle = 0

def on_simulation_stop(extension):
    print("Simulation Stopping") # requires parentheses


def pre_step(extension):
    extension.rotation_angle += 1

def post_step(extension):
    extension.outputs.rotation_transform.value = (
        Vortex.createRotation(0, 0, math.radians(extension.rotation_angle)))



def on_keyframe_save(extension, data):
    if hasattr(extension, "rotation_angle"):
        data["rotation_angle"] = extension.rotation_angle
    else:
        data["rotation_angle"] = 0

def on_keyframe_restore(extension, data):
    extension.rotation_angle = data["rotation_angle"]




Converting Vortex script code

  1. Change import VxSim to import Vortex
    1. Change all uses of VxSim in the code to Vortex
  2. Change the callbacks to their new names
    1. def on_add_to_universe(self, universe):      --> def on_simulation_start(extension):
    2. def on_remove_from_universe(self, universe): --> def on_simulation_stop(extension):
    3. def on_state_save(self, data):               --> def on_keyframe_save(extension, data):
    4. def on_state_restore(self, data):            --> def on_keyframe_restore(extension, data):
  3. (optional) Replace the self identifier with extension

We recommend changing self to extension, because self refers to the current instance of a class. However, in the case of Vortex scripts, what you receive in each callback is really the Vortex extension.

We also recommend not using global variables. It is generally bad practice and might not be supported in future versions. Instead you should store all data in the extension that is given as a parameter in each callback.
This means replacing all uses of "global my_var" to "extension.my_var".

Note that we removed access to the "universe" parameter in the new script callbacks. This parameter was only used for rare cases such as creation of Intersection Sensors and Sensor Triggers. In the Python 3 API, the universe is no longer required in this case. For more information and python code examples refer to the Sensors documentation.

Converting generic Python code

This part is trickier, it depends on the complexity of your code, if your scripts are not too complex you probably do not require any code changes.

Here are two simple examples that you might see in your code:

  1. The print statement requires parentheses in Python 3.
    print "A message" # Python 2
    print("A message") # Python 3


  2. Integer division returns a float in Python 3, whereas it would always return an integer in Python 2
    1. 3/2 = 1 in Python 2
    2. 3/2 = 1.5 in Python 3

There are more differences between Python 2 and Python 3, but there are better resources online for this information.

Reference Information

See https://www.python.org/ for help getting started if you are new to Python.

See Integrating Vortex Studio using Python 3 for more documentation about scripting with Vortex