There are times when it is necessary to read fixed data into ZEMAX for use during ray trace calculations. An example is data to define an arbitrary apodization function, for cases when this data cannot be fit to any of the existing models in ZEMAX. Such data can be read into ZEMAX using a user-defined surface. More information on how to create a user-defined surface may be found in the Knowledge Base article "How to Compile a User-Defined Surface".

An example (Reading_filter.ZMX) illustrating the use of a user-defined surface for defining an apodization function may be found in the .ZIP file located at the end of this article. This design contains two lenses: one to collimate light from an on-axis field point, and one to focus the beam back down to a point on the image plane. Situated between these lenses is a surface which acts as a transmission filter. The system is modeled with multiple configurations – an apodization function is applied to the intermediate surface in configuration 1 (providing finite values for the transmission of this surface) but not in configuration 2 (the transmission is therefore unity over the surface in this case). The apodization function used in configuration 1 is read in from the file Transmission.txt. This function is applied to the intermediate surface (surface 3 in this case) through the use of a user-defined surface. The source code and DLL for this surface (named US_FILT_FILE.C and US_FILT_FILE.DLL, respectively) are provided with your ZEMAX installation, and they are located in the directory \ZEMAX\DLL\. The transmission data file is provided in the .ZIP file located at the end of this article, and should be placed in the same directory (\ZEMAX\DLL).

The DLL is very similar to that used for defining the surface US_FILT (see Chapter 11 of the ZEMAX manual for more details). However, rather than specifying an exponential variation of the apodization function with the normalized radial coordinate (r), US_FILT_FILE.DLL allows the user to specify the value of the transmission at various values of r. In the data file, you need to specify the number of data elements in the first row, and then values for r and the associated transmission in subsequent rows:

Transmission data text file to be read into ZEMAX

In this DLL, values for the transmission at arbitrary values of r are calculated from linear interpolation of the inputted data.

As with any other DLL in ZEMAX, this DLL is called every time that rays are traced (e.g. in the Layout diagram, an analysis feature, etc.). To maximize computational efficiency, the ray trace calculations in ZEMAX are multi-threaded, meaning that there are generally many copies of the DLL which are being simultaneously opened during calculation. If it was necessary to read the data in to ZEMAX each time a copy of the DLL was made (e.g. each time a new analysis feature is opened or a current one is updated), this would slow down the computation time immensely. Fortunately, this is not the case. The data can be read in once, when the DLL is first loaded into ZEMAX (e.g. when the surface type is set to User Defined and the Surface DLL is then set to USER_FILT_FILE.DLL), during the DLL_PROCESS_ATTACH step:

BOOL WINAPI DllMain (HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved)
  {
  FILE *in;
  struct parse_type *pars;
  switch(ul_reason_for_call)
    {
    /* Read in the data when the DLL first loads */
    case DLL_PROCESS_ATTACH:
      /* Allocate memory for the transmission data */
      xtrans = malloc(MAX_PATH_LENGTH * (MAX_TEXT_PARS+1));
      trans = malloc(MAX_PATH_LENGTH * (MAX_TEXT_PARS+1));
      pars = malloc(sizeof(struct parse_type) * (MAX_TEXT_PARS+1));
      /* Open the text file containing the
         transmission data */
      in = NULL;
      if ((in = fopen("C:\\Program Files\\ZEMAX\\DLL\\Transmission.txt", "rt")) == NULL) return(0);
      /* Read in the number of data points */
      fgets(disp, MAX_PATH_LENGTH, in);
      n_trans = atoi(disp);
      /* Read in the transmission data */
      for (i=1; i<=n_trans; i++)
        {
        fgets(disp, MAX_PATH_LENGTH, in);
        Parser(disp, pars, MAX_TEXT_PARS);
        xtrans[i] = strtod(pars[1].n,NULL);
        trans[i] = strtod(pars[2].n,NULL);
        }
      /* Close the file, and free the memory associated
         with the text parser */
      if (in) fclose(in);
      in = NULL;
      if (pars) free(pars);
      pars = NULL;
      break;
    case DLL_PROCESS_DETACH:
      /* Free the memory associated with the transmission
         data once the DLL is unloaded */
      if (xtrans) free(xtrans);
      xtrans = NULL;
      if (trans) free(trans);
      trans = NULL;
      break;
    }
  return TRUE;
  }

During this step (which only occurs when the DLL is first loaded into ZEMAX), memory is allocated to those variables which will contain the coordinate and transmission data (in this case XTRANS and TRANS, respectively) as well as to a variable which will be used to read data in from the file (PARS). The file is then opened and data read from it. Once all of the data has been read and placed into the appropriate variables, the file is closed and the memory associated with the file-read variable is released. By defining XTRANS and TRANS to be global variables (i.e. by initializing them at the beginning of the program, before the DllMain call), this data is then available to the DLL for all subsequent ray traces:

/* define the global variables, which will be available
   during all calls to the DLL */
struct parse_type
  {
  char n[MAX_PARSE_LENGTH];
  };
int n_trans, i;
char disp[MAX_PATH_LENGTH];
double *xtrans, *trans;

Data could also have been read into this DLL using the case 8 flag under the switch(FD->type) within the DLL itself. However, case 8 – which corresponds to the DLL initialization flag – is called every time the DLL is called (e.g. if a new analysis feature is opened or a current one is updated). This method would therefore require that the data be read in multiple times. It is simply much more efficient to read the data in once during the DLL_PROCESS_ATTACH step. Since the data is read in only once, ray-tracing is not slowed by use of the external data.

The data is only unloaded from memory when the DLL is unloaded from ZEMAX, e.g. when ZEMAX is closed or the surface type is changed. At this point the DLL_PROCESS_DETACH step is initiated, during which the memory allocated for the global variables is released. If the DLL is then re-loaded into ZEMAX, data from the file will again need to be read in, but only once. If data in the file has changed and needs to be read back into ZEMAX, you can therefore unload the DLL (by changing the surface type) and then reload it, and the new data will be read in.

This example illustrates a useful way in which external data may be read into ZEMAX. This technique can be generalized for arbitrary surface and data types.