Skip to main content
Question

How to get a surface from one file and have it be the same surface in a different file Python PyZOS


Alex.Tripsas

Hi all,

 

I’ve been modifying .zmx files using Python for a few years now and have been using my own module and PyZOS for a majority of it now.  However, my current project has a bit of a road block I’m not sure how to get around.

 

What I would like to do is take a surface from a file I’ve already created and have it be the exact same surface in a different file.  I could have the code insert the surface, but if possible I’d like to avoid inserting and deleting surfaces as it may effect other parameters in the system (i.e. Merit Functions operands and whatnot).

What I’m hoping to code is something like the following

import zospy as zp

 

zos = zp.ZOS()

oss = zos.connect()

oss.load(‘file1’)

oss2 = zos.create_new_system()

oss2.load(‘file2’)

 

first_surface_file1 = oss.LDE.GetSurfaceAt(1)

first_surface_file2 = oss2.LDE.GetSurfaceAt(1)

 

#Modify somehow

 

first_surface_file1 == first_surface_file2 #would like to be True

 

I was thinking of somehow looping through the surface params but that’s proving difficult.  I was curious what others thought.

 

David.Nguyen
Luminary
Forum|alt.badge.img+2

@Alex.Tripsas 

 

What’s the problem with looping through the parameters?

I tried pickling (see https://stackoverflow.com/a/62420097/13198730), but it doesn’t work :(

import pickle
import zospy as zp

zos = zp.ZOS()
oss = zos.connect("extension")

s1 = oss.LDE.GetSurfaceAt(3)
s2 = oss.LDE.GetSurfaceAt(4)

print(pickle.dumps(s1) == pickle.dumps(s2))

Take care,


David


Alex.Tripsas

@David.Nguyen 

 

Pickling would have been a great approach.  Too bad it didn’t work out.  Yeah I was trying to figure out how to best loop through all the parameters.  My approach was something like:

s1 = oss.LDE.GetSurfaceAt(3)

for params in dir(s1):

    s1.params

But for obvious reasons that won’t work.

It’s a bit brute force still for my liking but I think the approach will be to loop through the cells of the surface using the GetSurfaceAt(i)

If I find a more elegant solution, I’ll post it here


David.Nguyen
Luminary
Forum|alt.badge.img+2

@Alex.Tripsas 

 

It could be a nice contribution to ZOSPy (https://zospy.readthedocs.io/en/latest/contributing.html).

Your approach is likely not working straight away because the surfaces have parameters of none native Python types. Such comparisons should be implemented as well for the whole thing to work. I still think what you propose is the most sensible approach though. It might be worth adding ​@Luc.van Vught, ​@chaasjes, and ​@jwbeenakker to this thread.

Take care,

 

David


Hi ​@Alex.Tripsas and ​@David.Nguyen,

We were actually discussing this tread on the side. It is a difficult problem to directly copy the surface.

 

One way would be looping through all the surface cells, and also through e.g. the ApertureData of the surface and such. This would also require you to check if any cells has a solver on it, and if so implement the same solver etc. That also brings you to the question: what to do if a surface cell has a Pickup solver. Overall, there are many edge cases that you have to consider for a robust copy.

 

For a quick and dirty solution, you could also look into the raw .zmx file data and see if you can copy it there, but I am not sure whether that is possible. I have not studies the file structure enough, but OpticStudio is able to compare surfaces using the built-in FileComparer, so there is some possibility there.

 

In the end, we thought maybe the best way forward is to use a multiple configuration editor. This allows you to keep the surfaces that you want to keep the same exactly as is, while you can swap out the surfaces in front or behind it. You can for example use the IGNM operant to ignore certain surfaces. We have a ZOSPy example that shows you how it works: Modelling of a Shack-Hartmann Sensor for eye aberration evaluation - updated example

 

Of course, if you built a robust copying method, please consider adding it to ZOSPy!

 

Best,

 

Luc


MichaelH
Ansys Staff
Forum|alt.badge.img+2
  • Ansys Staff
  • April 8, 2025

Hey Everyone,

I think there are 2 separate topics in this thread.

Comparing Surfaces

For comparing surfaces, ​@Luc.van Vught is right about the difficulty of checking 2 surfaces because not only do you need to consider the actual cells in the LDE, you’d also need to check everything in the ILDERow interface.  This includes:

  • TypeData
  • DrawData
  • ApertureData
  • ScatteringData
  • TiltDecenterData
  • PhysicalOpticsData
  • CoatingData
  • ImportData
  • CompositeData

Normally, you could write a few line recursive algorithm that would “crawl” through all the properties and populate a dictionary with all the values (similar to how you would list all the files in a folder & sub-folders).  In C# (and therefore in Python with pythonnet), you can use System.Reflection to get all the properties.  However, when I quickly tried to mock something up, I realized that you get into an infinite loop real quick because LDE.GetSurfaceAt(n) returns ILDERow but then when you get 1 level down with LDE.GetSurfaceAt(n).SurfaceData, you also get a property that return ILDERow.  So more logic would need to be applied to filter out existing entries to prevent an infinite loop.  I have added my first draft of Python code in case this helps in your effort (note this is failing at Line 21)

import clr
import System.Reflection

def GetPropValue(src, propName):
    return src.GetType().GetProperty(propName).GetValue(src, None)


def GetSurfaceData(obj, properties, output):
    # loop through all the reflected properties
    for property in properties:
        value = GetPropValue(obj, property.Name)

        # if the value is a primative, add it to the dictionary...if it's an interface, loop through child properties
        if isinstance(value, (int, str, bool, float)):
            output.update({property.Name: value})
        else:
            # get the children properties
            child_properties = list(value.GetType().GetProperties())
            if len(child_properties) > 0:
                # NEED TO ADD LOGIC TO CHECK IF PROPERTY IS ALREADY IN DICTIONARY BEFORE CALLING
                GetSurfaceData(value, child_properties, output)
    return output

# initialize the values (deep scan of Surface 1)
obj = TheSystem.LDE.GetSurfaceAt(1)
properties = list(obj.GetType().GetProperties())
output = {}

# call recursive function
GetSurfaceData(obj, properties, output)

Copying Surfaces

For copying surfaces, you can use the TheSystem.LDE.CopySurfacesFrom() method.  This actually allows you to do a deep copy of a surface, even from another file loaded into a second IOpticalSystem.  The first parameter asks for the ILensDataEditor you want to copy from so if you have 2 IOpticalSystem loaded into the script, you can do something like the following to copy the surface:

# use the primary system attached to the connection class
TheSystem.LoadFile(r'myfile.zmx', False)

# create a secondary system
TheSystem2 = TheSystem.TheApplication.CreateNewSystem(ZOSAPI.SystemType.Sequential)
TheSystem2.LoadFile(r'myfile2.zmx', False)

# copy S1 from System2 and insert at S2 in System1
TheSystem.LDE.CopySurfaces(TheSystem2.LDE, 1, 1, 2)

I believe this deep copy implements the same idea of using System.Reflection, but I think the ZOS-API code is a lot more robust than a quick 30 line algorithm.


Reply


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings