Zaber Launcher Tutorials
Zaber Motion Library
Sample Projects
Virtual DeviceDropdown icon
About3D Viewer
AccountDropdown icon
Sign InSign Up
Zaber Motion LibraryGetting Started
How-to Guides
Communication
Controlling multiple devicesFinding the right serial port nameImproving performance of X-USBDC (FTDI)Interaction with Zaber LauncherTCP/IP (network) communication
Library Features
Arbitrary unit conversionsError handlingEventsG-CodeNon-blocking (simultaneous) axis movementSaving and loading stateSending arbitrary commands
Device Features
Device I/OLockstepOscilloscopePosition Velocity Time (PVT)PVT Sequence GenerationSettingsStreamed movementTriggersWarning flags
Product-Specific APIs
MicroscopeProcess controllerVirtual devices
Advanced
Building a Standalone Application with MATLAB CompilerBuilding the C++ Library from Source CodeCustom transportsDevice databaseLoggingMATLAB Migration GuidePackaging Your Program with PyinstallerThread safety
API ReferenceSupportBinary Protocol (Legacy)
© 2026 Zaber Technologies Inc.

PVT Sequence Generation

This guide covers the generateVelocities , generatePositions and generateVelocitiesAndTimes functions of the PvtSequence class. If you are new to PVT, please refer to our introductory guide on the basics of setting up sequences and loading points.

PVT is a very useful feature of Zaber devices. It allows for precise control of timing and movement over continuous paths with many points, but path planning can involve solving for some unknowns. For some applications, a user may know the specific time and position for a point in a sequence, but not the velocity. In others, the user may know the times and velocities but not the positions. There may also be situations where the user knows only the positions. In any case, finding the best values to specify the desired motion requires either some fortuitous guess-work or a fairly comprehensive understanding of the math behind PVT.

This sequence generation API aims to simplify PVT path-planning and solve for these unknowns in a sensible way.

Sequence Generation API

PVT Sequence Data

All of the sequence generation functions return an array of PvtSequenceItem objects, which can either be saved as a CSV file using saveSequenceData , or used directly with the PvtSequence submitSequenceData method. Typically most of the items in the array are of type PvtPoint , but they are held in or represented by (depending on programming language) PvtSequenceItem because you can also interleave non-point PVT actions such as output port operations in the array, and they will be preserved.

For example:

Python
# Save as CSV
PvtSequence.save_sequence_data(generated, "my_pvt_sequence.csv")

# Add points (sequence must be in live or store mode)
pvt_sequence.submit_sequence_data(generated)
pvt_sequence.wait_until_idle()

Points input to the sequence generation functions are a different type, PvtPartialPoint , which allows for missing elements (or missing columns in the CSV format). These can be interleaved with the same non-point PVT action types if needed.

Notes on Times

For the sake the consistency of the trajectory and path plots, the example inputs for generateVelocities and generatePositions both have initial times set to 0. The generateVelocitiesAndTimes function will also return an initial time of zero.

If the initial point in a sequence has its time set to zero, the library treats it as a special "start position". Start positions must have an absolute position and zero velocity, and the device must already be at the start position when the sequence is submitted. The start position will not be submitted to the device as part of the PVT sequence.

If that behavior is undesirable, you must set the time of the first point to something greater than zero, giving the device enough time to move to the first point.

Here's an example showing how to move a two-axis device to the start position:

Python
device.get_axis(1).move_absolute(generated[0].positions[0].value, generated[0].positions[0].unit)
device.get_axis(2).move_absolute(generated[0].positions[1].value, generated[0].positions[1].unit)

Generally, the Zaber Motion Library PVT functions expect the point times to be relative. That is, relative to the previous point. The opposite condition, called absolute time, is when each point's time value is relative to the beginning of the sequence. There are functions to convert times between relative and absolute, for example:

Python
points = PvtSequence.convert_time_absolute_to_relative_partial(points)

Generate Velocities (generateVelocities )

This function works by generating velocities such that acceleration is continuous over the entire sequence. It will also set the start and end velocities to zero unless otherwise specified (see With Partially Defined Velocities section below).

In this example we are generating the velocities for a 2-dimensional spiral path. The inputs to the sequence generation functions are always arrays of PvtPartialPoint , which allows the positions and velocities to be empty arrays or to contain null measurements, and allows the time measurement to be null. Only some combinations of missing measurements are supported by the sequence generation functions.

Python
points = [
    PvtPartialPoint(positions=[Measurement(14.5, "mm"), Measurement(7.5, "mm")], velocities=[], time=Measurement(0, "s")),
    PvtPartialPoint(positions=[Measurement(15.943, "mm"), Measurement(6.666, "mm")], velocities=[], time=Measurement(3.333, "s")),
    PvtPartialPoint(positions=[Measurement(11.613, "mm"), Measurement(5.833, "mm")], velocities=[], time=Measurement(6.667, "s")),
    PvtPartialPoint(positions=[Measurement(14.5, "mm"), Measurement(12.5, "mm")], velocities=[], time=Measurement(10, "s")),
    PvtPartialPoint(positions=[Measurement(20.274, "mm"), Measurement(4.167, "mm")], velocities=[], time=Measurement(13.333, "s")),
    PvtPartialPoint(positions=[Measurement(7.283, "mm"), Measurement(3.333, "mm")], velocities=[], time=Measurement(16.667, "s")),
    PvtPartialPoint(positions=[Measurement(22, "mm"), Measurement(15, "mm")], velocities=[], time=Measurement(20, "s")),
]

Then make the call to generate the velocities for the sequence. Note that while you can specify the data with either relative or absolute time, Then make the call to generate the velocities for the sequence. Note that while you can specify the data with either relative or absolute time, the sequence generation functions expect input times to be relative. Additionally, the time sequences in the output data are always relative. This is because submitSequenceData expects times to be relative. This example includes a call to convert to relative time:

Python
points = PvtSequence.convert_time_absolute_to_relative_partial(points)
generated = PvtSequence.generate_velocities(points)

Path and Trajectory with Generated Velocities

Note that there is no guarantee that the generated sequence will adhere to your axes' velocity and acceleration constraints, so it is important to provide 'reasonable' times which allow for enough travel time from point to point.

With Partially Defined Velocities

Optionally, you can pass in a partially-defined sequence of velocities. In this case the function will generate velocities with continuous acceleration for all of the undefined segments of the path.

Python
points[3].velocities = [Measurement(5, "mm/s"), Measurement(0, "mm/s")]
generated = PvtSequence.generate_velocities(points)

Since the start and end velocities are set to zero if undefined, the above velocity example is equivalent to:

Python
points[0].velocities = [Measurement(0, "mm/s"), Measurement(0, "mm/s")]
points[3].velocities = [Measurement(5, "mm/s"), Measurement(0, "mm/s")]
points[6].velocities = [Measurement(0, "mm/s"), Measurement(0, "mm/s")]
generated = PvtSequence.generate_velocities(points)

The call to generate velocities produces the following path:

Path and Trajectory with Partially Generated Velocities

Notice that acceleration is not continuous between the path segments on either side of the defined point. This is because the function actually splits the path into two sections and solves each separately, so while the continuity constraint applies over the first and second sub-paths, it isn't enforced over the entire path.

Generate Positions (generatePositions )

Like generateVelocities , this function also enforces that acceleration is continuous from point to point. Additionally, it uses the constraint that acceleration is 0 at the start point.

In this example we are generating the positions for a 1-dimensional rotary path with velocities and times:

Python
velocities = [
    PvtPartialPoint(positions=[], velocities=[Measurement(0, "°/s")], time=Measurement(0, "s")),
    PvtPartialPoint(positions=[], velocities=[Measurement(180, "°/s")], time=Measurement(3, "s")),
    PvtPartialPoint(positions=[], velocities=[Measurement(-180, "°/s")], time=Measurement(6, "s")),
    PvtPartialPoint(positions=[], velocities=[Measurement(180, "°/s")], time=Measurement(6, "s")),
    PvtPartialPoint(positions=[], velocities=[Measurement(360, "°/s")], time=Measurement(3, "s")),
    PvtPartialPoint(positions=[], velocities=[Measurement(0, "°/s")], time=Measurement(6, "s")),
    PvtPartialPoint(positions=[], velocities=[Measurement(-180, "°/s")], time=Measurement(3, "s")),
    PvtPartialPoint(positions=[], velocities=[Measurement(-360, "°/s")], time=Measurement(3, "s")),
    PvtPartialPoint(positions=[], velocities=[Measurement(0, "°/s")], time=Measurement(6, "s")),
]

Then, the call to generate positions produces the following trajectory. Notice that in this case we have specified times to be relative (the default value for this parameter is true).

Python
generated = PvtSequence.generate_positions(velocities)

Trajectory with Generated Positions

Generate Velocities and Times (generateVelocitiesAndTimes )

This function works differently than generateVelocities and generatePositions . It first fits a geometric spline over the position sequence and then calculates the velocity and time information by traversing the spline using a trapezoidal motion profile. While it will produce a smooth trajectory, it does not guarantee that velocity will be continuous over the entire path. It is also only capable of generating trajectories for single axis linear and rotary trajectories, and multi-axis linear trajectories where the directions of all axes are orthogonal (ie. Cartesian systems).

In this example, we are generating the same 2-dimensional spiral path using only position information.

Python
positions = [
    PvtPartialPoint(positions=[Measurement(14.5, "mm"), Measurement(7.5, "mm")], velocities=[]),
    PvtPartialPoint(positions=[Measurement(15.943, "mm"), Measurement(6.666, "mm")], velocities=[]),
    PvtPartialPoint(positions=[Measurement(11.613, "mm"), Measurement(5.833, "mm")], velocities=[]),
    PvtPartialPoint(positions=[Measurement(14.5, "mm"), Measurement(12.5, "mm")], velocities=[]),
    PvtPartialPoint(positions=[Measurement(20.274, "mm"), Measurement(4.167, "mm")], velocities=[]),
    PvtPartialPoint(positions=[Measurement(7.283, "mm"), Measurement(3.333, "mm")], velocities=[]),
    PvtPartialPoint(positions=[Measurement(22, "mm"), Measurement(15, "mm")], velocities=[]),
]

Then we generate the velocities and times for the sequence with a target speed of 10mm/s and target acceleration of 5mm/s².

Python
target_speed = Measurement(10, "mm/s")
target_accel = Measurement(5, "mm/s²")
generated = PvtSequence.generate_velocities_and_times(positions, target_speed, target_accel)

Path and Trajectory with Generated Velocities and Times

Notice that acceleration and velocity don't adhere perfectly to our target values. The following section on resampling provides a means of keeping the trajectory closer to the specified velocity and acceleration targets.

With Resampling

Resampling allows you to specify the number of sample points along the geometric spline which is fitted to the input positions. For PVT paths with few positions, this can help the function to build a trajectory which adheres better to the velocity and acceleration targets. For PVT paths with many positions, this can help to reduce the size of the final generated sequence.

We will be upsampling the path from above using the same target speed and acceleration values and a resample value of 30, so that there will be 30 PVT points in the returned sequence data object.

Python
generated = PvtSequence.generate_velocities_and_times(positions, target_speed, target_accel, 30)

Path and Trajectory with Generated Velocities and Times Resampled

Notice that after the initial curve in the path, the trajectory stays much closer to the target speed and acceleration than in the previous example.

It is important to note that resampled paths may not pass through the exact positions in the input sequence. This is because the input positions likely won't be included in the generated trajectory. The following plot illustrates this clearly, with the red x's representing the original input positions:

Path with Generated Velocities and Times Resampled with Original Positions

If for your application you require your device to pass through the input positions exactly, then resampling may not be the tool for you. That said, a higher resample value will likely reduce the amount of deviation.

Also note that resampling does not work on sequences that contain non-point actions, because their relative timing would be lost.

Further Reading

  • Mastering Motion: Position-Velocity-Time Control for Smooth and Precise Path Planning
  • PVT Sequence Generation (with helper functions for visualization)

Resources

  • ASCII Protocol Manual - PVT (firmware version 7.33)
  • API Reference
    • PvtSequence
    • PvtSequenceData
    • MeasurementSequence
    • OptionalMeasurementSequence