How to build and optimize a singlet using ZOS-API with Python using COM

The ZOS-API is a powerful tool which enables users to perform programmatically all the actions (and more) that are possible in the OpticStudio GUI. In this article, we will show how to use the ZOS-API with Python to create a singlet lens, add Merit Function targets, and optimize the system. This example is using COM via the pywin32 module to connect Python to the ZOS-API.

Authored By Thomas Pickering


Article Attachments


This example uses COM via the pywin32 module to connect Python to the ZOS-API. This is no longer the recommended way to connect. For more information, see this article ZOS-API using Python.NET.

In this article, we will walk through setting up and optimizing a singlet lens system via the use of the Python connection in the ZOS-API. We will utilize the Standalone Application feature which allows users to write their code in their default Python program and then run it to obtain a working OpticStudio file. 

Open the boilerplate template

For the purpose of this article we will focus on the Standalone Mode. The first step is to create a boilerplate solution, so select Programming...Python...Standalone Application.

new standalone app

A Windows Explorer opens with the solution folder {Zemax}\ZOS-API Projects\PythonStandaloneApplication.

The Python project and script was tested with the following tools:

Note that Visual Studio and Python Tools make development easier, however the Python script should run without either installed. If Visual Studio is installed, it opens with your new solution. The solution contains a boilerplate code that can be used as the basis for any Standalone Application.

Make a new file and apply system settings

We will design a singlet lens with the following specifications:

  • Light comes from infinity, with a 5 degree semi-field of view, and a single wavelength (d-line, .587 mm)
  • Collimated input light is to be focused to smallest RMS spot size, averaged across the field of view
  • F/10, EPD 40 mm
  • Made from N-BK7
  • Stop is a separate surface and is free to move but comes after the lens
  • Lens should be at least 3, and no more than 15 mm thick at center
  • Lens should have a minimum edge thickness of 3 mm, and air gaps should be a minimum of 0.5 mm

In your script, go to “# Insert Code Here” and then enter:

TheSystem = zosapi.TheSystem;

This represents a complete optical system, which corresponds to a single .ZMX file. Now create a string containing the file path for the new file. To make the script flexible, we will use the SamplesDir property to create file path .

fileOut = zosapi.TheApplication.SamplesDir + "\Sequential\Objectives\Single Lens Example wizard+EFFL.zmx";

TheSystemData holds all the basic data of the System. We can use this to set aperture, field points, wavelengths, etc.

# Aperture
TheSystemData = TheSystem.SystemData;
TheSystemData.Aperture.ApertureValue = 40;

# Fields

# Wavelength preset

For enumerated constants like WavelengthPreset, Python creates each constant as {enum name}_{enum value}.

Set up the Lens Data Editor 

In the Lens Data Editor (LDE), each row represents a surface defined in the System: Surface 0 is the Object and the last Surface is the Image. For a simple lens located before the stop, the front side of the lens would be Surface 1, the rear side of the lens is Surface 2 and the Stop would be at Surface 3.

# Lens data
TheLDE = TheSystem.LDE;
Surface_1 = TheLDE.GetSurfaceAt(1);
Surface_2 = TheLDE.GetSurfaceAt(2);
Surface_3 = TheLDE.GetSurfaceAt(3);
Surface_1.Thickness = 10.0;
Surface_1.Comment = 'front of lens';
Surface_1.Material = 'N-BK7';
Surface_2.Thickness = 50.0;
Surface_2.Comment = 'rear of lens';
Surface_3.Comment = 'Stop is free to move';
Surface_3.Thickness = 350.0;

We have 5 independent variables, which we can use to achieve our goal – the front and back radius of the lens, the lens thickness, the location of the stop and the image location.

# Make thicknesses and radii variable

If we want to open our file in OpticStudio to check the progress at this point, we will need to first save it using the following code:

# Save and close
The System.Save();

This code will need to remain at the end of anything else you type, but we will use it now to check the system. Make sure that anything you add after this comes before this code in the editor. 

When we run the code and receive no errors, we can open the lens file by navigating to the directory we specified above: {Zemax}\Samples\Sequential\Objectives and selecting our file. The LDE will look as follows.

lens data


Set up the Merit Function Editor

We will use the Sequential Optimization Wizard to make a Default Merit Function target the Minimum Spot Radius also specifying the Glass/Air Boundary Values.

merit function editor

To set up the Optimization Wizard as in the previous figure we use the following code:

# Merit functions
TheMFE = TheSystem.MFE;    

wizard = TheMFE.SEQOptimizationWizard;

wizard.Type = 0;        # RMS
wizard.Data = 1;        # Spot Radius
wizard.Reference = 0;   # Centroid
wizard.Ring = 2;        # 3 Rings
wizard.Arm = 0;         # 6 Arms

wizard.IsGlassUsed = True;
wizard.GlassMin = 3;
wizard.GlassMax = 15;
wizard.GlassEdge = 3;

wizard.IsAirUsed = True;
wizard.AirMin = 0.5;
wizard.AirMax = 1000;
wizard.AirEdge = 0.5;

wizard.IsAssumeAxialSymmetryUsed = True;

The OK button method is part of the CommonSettings of all wizards available in OpticStudio.

Since the lens is before the stop, we cannot use a F/# solve. We don’t know which ray is the marginal ray until it has hit the edge of the stop. This means we must use the EFFL operand in addition to the RMS spot default merit function. Just add an extra line at the top, target the EFL to 400mm. Remember to add a weight of 1.

Operand_1.Target = 400.0;
Operand_1.Weight = 1.0;

After this, the Merit Function will look like this.

merit function editor 2nd pic

Local Optimization

Now let’s access the properties and methods for running a Local Optimization and set it up like in the Graphical User Interface.

local optimization

# Local optimisation till completion
LocalOpt = TheSystem.Tools.OpenLocalOptimization();
LocalOpt.Algorithm = constants.OptimizationAlgorithm_DampedLeastSquares;
LocalOpt.Cycles = constants.OptimizationCycles_Automatic;
LocalOpt.NumberOfCores = 8;
baseTool = CastTo(LocalOpt, "ISystemTool")

Due to how the Python COM interface handles inheritance, OpenLocalOptimization() returns an ILocalOptimization interface, while all of the run/close/etc. are on the ISystemTool interface. Therefore, you need to cast the derived class object to a base class object to get access to things that live in the base.

Finally, save the file after the optimization finishes.

# Save and close


After running your script, open your .ZMX file in OpticStudio and check out your fully optimized lens.

fully optimized lens


Was this article helpful?
1 out of 4 found this helpful



Article is closed for comments.