Introduction to Zemax Programming Language (ZPL)

This article is part of the ZPL Programming free tutorial.

The Zemax Programming Language (ZPL) allows users to write their own programs within OpticStudio. These programs can:

  • Automate repetitive keyboard and mouse actions
  • Perform calculations based on OpticStudio data
  • Export data in specific formats
  • Produce graphs and text listings of data

and much, much more!

Creating ZPL macros is easy. This article provides an overview of the key ZPL features, as well as examples of variables and description of the most important functions and keywords.

Authored By Dan Hill, updated by Alessandra Croce


ZPL is a case-insensitive “macro” or “scripting” language, and represents the simplest method of extending OpticStudio’s range of calculations. It is modelled after BASIC and it is an interpreted language. While this means that it’s very easy to code ZPL macros, it also implies that execution will be slower than fully compiled codes for complex calculations.

ZPL scripts can, however, call compiled functions (keywords and operands) inside OpticStudio, and can be of great use in the following cases:

  • When you need a unique format for data
  • To implement features or calculations not in the program, such as data retrieval, export, or simple plotting
  • To optimize when there is no appropriate operand (custom operand creation)
  • To create custom/complex solves (custom solve creation)
  • To automate repetitive keyboard activity

Note that ZPL cannot be used to provide user-defined surfaces or objects. For this purpose, you can use DLLs instead.

ZPL consists of five basic concepts: variables, operations, functions, keywords and comments. These concepts are all introduced below, however more information can be found in OpticStudio help file section The Programming Tab > About the ZPL

The goal of the ZPL is to give optical engineers, who may not have much programming experience, a powerful programming tool which executes quickly and is easy to learn. Note, however, that OpticStudio also supports ZOS-API, which is an API-level interface allowing programmers access to OpticStudio’s features from external programs, such as MATLAB or Python. See "What is ZOS-API and what can it be used for?" for more details

Assignments, variables and arrays

The basic syntax for an assignment is:

variable = expression

where “ = ” means “set the value to”.

Variables can be either string or numeric, depending upon the type of data they contain. Expressions can be explicit values, other variables, or complex mathematical expressions/functions. Only array variables need to be declared before use, so for example x = 7 works without declaring the numeric variable x first. The following are examples of valid assignments:

x = 4
y = 3
z = x + y

String variables must have a $ character as the last character in their name:  

newstring$ = "Here is the new string"

Variables are always held as double-precision 64-bit values. There are a few limitations on their names that are to be respected. Variable names should not:

  • Be longer than 28 characters
  • Include any special character that is used as operator (*, !, <, &, etc.)
  • Include any space (use “ _ ” instead)
  • Be the same of a keyword or a function


Array (or vector) variables are also supported in ZPL. There are 4 predefined vectors, called VEC1, VEC2, VEC3, and VEC4, mainly used by other commands, such as GETPSF, GETMTF, etc., Their size is set by SETVECSIZE keyword.

User-defined array variables must be declared with the DECLARE keyword before use. They can be made of up to 4 dimensions and hold double or integer data types. RELEASE keyword must be used to free the memory allocated to the array.

User-defined array values can be assigned using this syntax:

arrayname(index1, index2, …) = value

Values can be retrieved using this syntax instead:

value = arrayname(index1, index2, …)

Keywords and functions

Commands in ZPL fall into two categories:

  • Keywords make OpticStudio do or change something, for example:
    • RAYTRACE traces a specific ray through the system
    • SETUNITS sets the system units
    • PRINT prints something to screen or file
  • Functions make OpticStudio report on something, such as:
    • y_height = RAYY(5), reporting the Y-coordinate of the ray intercept on surface 5
    • lens_units$ = $UNITS(), reporting the system’s lens units

Here we will give a few examples of both keywords and functions, however they span the whole range of ray-tracing and physical optics calculations available in OpticStudio. Please refer to OpticStudio help files for a complete listing of all supported functions, keywords, and general ZPL capabilities.


Keywords and functions are calls to compiled routines inside OpticStudio, and are usually very similar to how the relevant feature works in the Ribbon Bar.





They execute as fast as the internal commands, with just a small overhead for calling the command. So don’t consider ZPL to be “slow”! Do bear in mind, however, that calculations performed explicitly inside the macro are interpreted, and are therefore slow in comparison to a compiled program. If calculation speed inside the macro is important, use ZOS-API.NET instead.

Keywords can direct program flow (IF or FOR statements), or perform tasks such as ray tracing or modifying the lens prescription. For example, to tell OpticStudio to optimize a system for 10 cycles, you may send the command:


Again, this executes exactly the same code as when you press the Optimize button and select 10 Cycles in the graphical user interface. 

Keywords and Operands

In the early days of Zemax, keywords like GETMTF and GETZERNIKE were written to allow access to the corresponding analysis features. However, as the code base grew, it soon became apparent that this would become tedious. Therefore, access to most analysis functions is via their associated optimization operand. This can be performed via the merit function, or directly by calling the operand itself from the macro, using OPEV() or OPEW() numeric functions. See "How to obtain the value for any optimization operand in a ZPL macro using OPEV and OPEW" for further details.


As previously mentioned, functions differ from keywords as they don’t do or change anything in the file, instead they make OpticStudio report on something. ZPL supports numerous functions, both numeric and string. Example of numeric functions are:

x = SQRT(y)

y = ABSO(z*TANG(e*numpoints))

pi = 4*ATAN(1)

Instead, string functions begin with a $ symbol, such as:



surf3_glass$ = $GLASS(3)

Unlike keywords, functions can only be used on the right-hand side of an assignment, or in expressions which are arguments to keywords. Certain functions, such as PWAV() (reporting the system’s primary wavelength), return a value which does not depend upon the argument, and therefore it is not required to provide one. The parentheses, however, are still required.

Operations and logical operators

ZPL supports basic numeric operations (addition, subtraction, division, multiplication) and string operations (addition). Examples of both operations are:

Z = (X + 1)/(Y * 6)

R$ = "Hello " + "World", which assigns string "Hello World" to variable R$

Other numeric and string functions, such as SINE(), SQRT(), $GETSTRING(), etc., can be used for more complex operations. The order of operations can be customized using parentheses, with the default otherwise being: functions (e.g. SQRT()), logical operators (e.g. >), multiplication and division, addition and subtraction.

ZPL also supports logical operators, both numeric and string. These can be used for logical tests and comparisons on multiple expressions. The result of a logical operation is either true (any non-zero value) or false (zero). Similarly, OpticStudio treats zero as FALSE and any non-zero value as TRUE.

Below is a table of numeric logical operators:

| (OR) < (LESS THAN)
^ (XOR) > = (GREATER or EQUAL)
! (NOT) < = (LESS or EQUAL)
= = (EQUAL) ! = (NOT EQUAL)


An example of numeric logical operator is:

IF(A > 5) THEN PRINT "A is greater than 5"

Below is a table of string logical operators

$ = = (EQUAL) $ > = (GREATER or EQUAL)
$ > (GREATER THAN) $ < = (LESS or EQUAL)
$ < (LESS THAN) $ ! = (NOT EQUAL)


An example of string logical operator is:

IF (C$ $== B$) THEN PRINT "Strings are identical"

Assignments vs. logical operators

It is common to mistake assignment operator “ = ” and logical operator “ = = ”.

For example, consider the macro below:


In the macro, “a = b” is an assignment, and reads “set the value of variable a equal to that of variable b”. OpticStudio sets the value of a equal to b and returns a logical TRUE because the operation succeeded. Therefore, the macro reads:


PRINT "a equals b"


PRINT "a does not equal b"


In this case, OpticStudio traps the error and returns:

Syntax error: Illegal assignment (a = b).

The correct behavior can be obtained instead using logical operation “a == b”.

IF and FOR conditional loops

IF and FOR conditional loops are both supported in ZPL.

Syntax for IF loops is the following:

IF (expression)






IF (expression) THEN (single command)

IF loops are typically used with logical operators as tests in expression statements. For example:

IF (X < Y)

PRINT "X is less than Y"


PRINT "X is greater than Y"


Note that it is left to the user to trap errors, e.g. what happens if x = y ?

Syntax for FOR loops is the following:

FOR variable, start_value, stop_value, increment



For example:

FOR i, 1, 10, 1



The following syntax, defining variable and start value at the same time, is also valid:

FOR i = 1, 10, 1

Note that start_value and stop_value can be explicit numbers as well as any variable or expression that evaluates to a number. The increment should be an integer, or any variable or expression that evaluates to an integer

Calling Macros and Subroutines

Keyword CALLMACRO allows one macro (parent) to call another one (child). Numeric and string buffers allow data to be shared between parent and child macros. Sample macros “Parent.ZPL” and “Child.ZPL” are included in OpticStudio installation files and are described in help file section The Programming Tab > About the ZPL > Calling a Macro from within a Macro

Keyword GOSUB allows execution to jump to a subroutine indicated by SUB. Control returns to the line following GOSUB keyword once the subroutine has finished executing. Subroutines can be called multiple times throughout the macro. Note that they are defined in the same macro, they are not separate macros as in the parent/child case previously described. More information on subroutines is provided in OpticStudio help file section The Programming Tab > About the ZPL > KEYWORDS (about the zpl) > GOSUB, SUB, RETURN, and END

RAYTRACE, RAYTRACEX and associated functions

For macros to be used in sequential mode, one of the most important keywords is RAYTRACE, which traces a single ray (identified by normalized field and pupil coordinates) of a given wavelength; the keyword does nothing else. The syntax is:

RAYTRACE hx, hy, px, py, wave

For example:

RAYTRACE 0, 1, 0, 0, PWAV(), traces the chief ray at the primary wavelength.

You can then use subsequent functions to extract any relevant ray trace data, such as:

  • RAYX(x), RAYY(x), and RAYZ(x), returning the ray’s X- Y- and Z-coordinate on surface x
  • RAYL(x), RAYM(x), and RAYN(x), returning the ray’s direction cosines on surface x
  • RANX(x), RANY(x), and RANZ(x), returning the surface normal direction cosines where the ray hit surface x
  • RAYT(x), returning the ray’s optical path length up to surface x
  • OPDC(), returning the ray’s optical path difference with respect to the chief ray

For example, the following code traces the marginal ray at wavelength 2. Then extracts and prints to screen the Y-coordinate intercept of the ray on the image surface:

RAYTRACE 0, 0, 0, 1, 2

PRINT "The chief ray height is ", RAYY(NSUR())

Function RAYE() reports on any errors that might have occurred when tracing the ray:

  • Zero is returned if no errors have occurred during the ray trace
  • A negative value indicates that Total Internal Reflection has occurred at the surface whose number is the absolute value of the value returned
  • A positive value indicates the ray missed the surface number returned

Checking RAYE() is optional, however the data extracting functions described above may return invalid data if RAYE() doesn’t return zero.

Function RAYV() can be used to checked if the ray was vignetted i.e. blocked by an aperture. The function returns the surface number at which the ray was vignetted, or zero if the ray was not vignetted. Remember that rays are still traced if they hit apertures, and can be excluded from calculations by testing with RAYV() if vignetting has occurred.

RAYTRACEX keyword is a very useful extension to RAYTRACE. It calls OpticStudio ray tracing routines to trace a particular ray from any starting surface through the current system. The syntax is:

RAYTRACEX x, y, z, l, m, n, surf, wave

Data about the ray trace can then be extracted using the same subsequent functions as RAYTRACE, as described above.

Commenting in ZPL

Finally, comments allow you to document how your ZPL program works. Any line that starts with “ ! ”, and any text that follows “ # ”, is treated as a comment and ignored. OpticStudio will only execute the script that wraps these components together.

For example, imagine that you need to optimize a lens, compute the merit function value and multiply this value by the log of 5. To do this, the ZPL macro is simply:


The execution of a macro in OpticStudio is typically very fast, and ZPL is a simple language to learn and use. To help get you started, there are several articles within the Knowledgebase that demonstrate some of the capabilities of the Zemax Programming Language, and many samples are supplied with your OpticStudio installation.


This article is the first component of the ZPL Programming free tutorial.

Next article: How to write a ZPL macro


Was this article helpful?
28 out of 35 found this helpful



Please sign in to leave a comment.