Installation instructions for Anaconda and Python for .NET
Examples presented here are based on Python 3.5
Examples are shown in jupyter notebook application. You can use the same code in the spyder.exe (winPython IDE)
You can run examples from the notebook itself (this browser window), or use spyder scripts
Examples shown in early parts of this notebook are simple, barebone scripts in order to explain the communication process with LightTools. A more flexible, easy to use function library project is decribed later in this presentation. Project name: LTPython
Macro examples
Passing a message, commands, and data access with plotting (XY Scatter)
Mesh data access and plotting (2D Raster)
Parameter study example
Simple 2-variable example
Focus with conic lens
Collimator with Swept/Bezier
SplinePatch example
With Anaconda and Python for .NET installed, the installation is complete. The next step in writing a macro is to connect to the .NET librarries.
In [1]:
# Import the packages/libraries you typically use
import clr
import System
import numpy as np
import matplotlib.pyplot as plt
#This forces plots inline in the Spyder/Python Command Console
%matplotlib inline
#In the line below, make sure the path matches your installation!
LTCOM64Path="C:\\Program Files\\Optical Research Associates\\"
LTCOM64Path=LTCOM64Path + "LightTools 8.4.0\\Utilities.NET\\LTCOM64.dll"
clr.AddReference(LTCOM64Path)
from LTCOM64 import LTAPIx
lt0=LTAPIx()
#If PID capabilities (for multiple LightTools sessions) needed, use the PID for the session you want
#lt0.LTPID=12040
lt0.UpdateLTPointer
#If no PID is specified, connect to the first running session
Out[1]:
In [3]:
lt0.Message("Hello from jupyter Notebook - 2!")
Out[3]:
In [4]:
#Set the focus to the 3D Window, pass a fixed command string to create a sphere with radius 5
lt0.Cmd('\V3D ctrsphere xyz 0,0,0 xyz 0,0,5')
Out[4]:
In [4]:
cmdstr="ctrsphere " + lt0.Coord3(0,0,0) + lt0.Coord3(0,0,5)
print(cmdstr) #so that we can see it
lt0.Cmd(cmdstr)
Out[4]:
In [5]:
#Set the radius to 10
key="Solid[@Last].Primitive[1]"
lt0.DbSet(key,"Radius",10)
r=lt0.DbGet(key,"Radius")
print("Radius of the sphere is: " + str(r))
In [12]:
from IPython.display import Image
Image(filename = PATH + 'BooleanAndMove.PNG',width=500,height=100)
Out[12]:
In [66]:
cmdstr="Cylinder " +lt0.Coord3(0,0,0) + " 3 15" #radius =3, length = 15
lt0.Cmd(cmdstr)
#Get the names of the objects. We have 2 objects
#Notice that we are using the "index" of each solid object
names=[]
for i in [1,2]:
key="Solid[" + str(i) + "]"
print("Current data key is: " + key) #so that we can see it
names.append(lt0.DbGet(key, "Name"))
print(names[i-1])
#Select two objects
lt0.Cmd("Select " + lt0.Str(names[0]) + " More " + lt0.Str(names[1]))
lt0.Cmd("Subtract")
#Resulting object has te name of the first selected object for boolean
lt0.Cmd("Select " + lt0.Str(names[0]))
lt0.Cmd("Move " + lt0.Coord3(0,10,10))
Out[66]:
In [94]:
#Get the spectral power distribution from a receiver (1D grids)
key="receiver[1].spectral_distribution[1]"
cellcount=int(lt0.DbGet(key,"Count"))
print("Number of rows: " + str(cellcount))
w=np.zeros((cellcount))
p=np.zeros((cellcount))
for i in range(1,cellcount+1,1):
w[i-1],stat=lt0.DbGet(key,"Wavelength_At",0,i,1) #data returned is a tuple!
p[i-1],stat=lt0.DbGet(key,"Power_At",0,i,1)
plt.plot(w,p,'-r')
Out[94]:
In [5]:
#Get the mesh data one cell at a time (this is a 2D grid)
# Note that a faster method for mesh data is described below
key="receiver[1].Mesh[1]"
xdim=int(lt0.DbGet(key,"X_Dimension")) #Columns
ydim=int(lt0.DbGet(key,"Y_Dimension")) #Rows
cv=np.zeros((ydim,xdim))
for i in range(1,xdim+1,1):
for j in range(1,ydim+1):
cv[j-1,i-1],stat=lt0.DbGet(key,"CellValue",0,i,j)
#Get the mesh bounds
MinX=lt0.DbGet(key,"Min_X_Bound")
MaxX=lt0.DbGet(key,"Max_X_Bound")
MinY=lt0.DbGet(key,"Min_Y_Bound")
MaxY=lt0.DbGet(key,"Max_Y_Bound")
#Create a data grid for plotting, and plot the data
xvec=np.linspace(MinX,MaxX,xdim+1)
yvec=np.linspace(MinY,MaxY,ydim+1)
X,Y=np.meshgrid(xvec,yvec)
plt.pcolormesh(X,Y,cv,cmap='jet')
plt.xlabel("X")
plt.ylabel("Y")
plt.axis("equal")
#See below for a simpler/faster method to access mesh data
Out[5]:
In [7]:
def GetLTMeshParams(MeshKey,CellValueType):
"""Get the data from a receiver mesh.
Parameters
----------
MeshKey : String
data access string for the receiver mesh
CellValueType : data type to retrieve
Returns
-------
X_Dimension
Number of bins in X dimension
Y_Dimension
Number of bins in Y dimension
Min_X_Bound
Minimum X bound for the mesh
Max_X_Bound
Maximum X bound for the mesh
Min_Y_Bound
Minimum Y bound for the mesh
Max_Y_Bound
Maximum Y bound for the mesh
Mesh_Data_Array
An array of data, based on the cell value type requested
Examples
--------
meshkey="receiver[1].Mesh[1]"
xdim,ydim,minx,maxx,miny,maxy,md=GetLTMeshParams(meshkey,"CellValue")
"""
XDim=int(lt0.DbGet(MeshKey,"X_Dimension"))
YDim=int(lt0.DbGet(MeshKey,"Y_Dimension"))
MinX=lt0.DbGet(MeshKey,"Min_X_Bound")
MaxX=lt0.DbGet(MeshKey,"Max_X_Bound")
MinY=lt0.DbGet(MeshKey,"Min_Y_Bound")
MaxY=lt0.DbGet(MeshKey,"Max_Y_Bound")
# We need a double array to retrieve data
dblArray=System.Array.CreateInstance(System.Double,XDim,YDim)
[Stat,mData]=lt0.GetMeshData(MeshKey,dblArray,CellValueType)
MeshData=np.ones((XDim,YDim))
print(XDim,YDim)
for i in range(0,XDim):
for j in range(0,YDim):
MeshData[i,j]=mData[i,j]
#print(mData[i,j])
MeshData=np.rot90(MeshData)
#Notice how we return multiple data items
return XDim,YDim,MinX,MaxX,MinY,MaxY,MeshData
In [8]:
import matplotlib
meshkey="receiver[1].Mesh[1]"
xdim,ydim,minx,maxx,miny,maxy,md=GetLTMeshParams(meshkey,"CellValue")
cellx=np.linspace(minx,maxx,xdim+1)
celly=np.linspace(miny,maxy,ydim+1)
X,Y=np.meshgrid(cellx,celly)
#Raster chart in LOG scale
plt.pcolormesh(X,Y,np.flipud(md),cmap="jet",norm=matplotlib.colors.LogNorm())
plt.colorbar()
plt.axis("equal")
plt.xlabel("X")
plt.ylabel("Y")
Out[8]:
In [5]:
from LTCOM64 import JSNET2
js=JSNET2()
#If PID capabilities (for multiple LightTools sessions) needed, use the PID for the session you want
#js.LTPID=12040
js.UpdateLTPointer
Out[5]:
In [10]:
js.MakeSphere(5,"mySphere")
js.MoveVector("mySphere",0,10,10)
# js.MoveVector("mys*",0,10,10) will move all objects whose name starts with 'mys'
Out[10]:
In [3]:
#First, let's create a simple function to add a new optical property
#This will create a new property, and return the name
def AddNewProperty(propname):
lt0.Cmd("\O" + lt0.Str("PROPERTY_MANAGER[1]"))
lt0.Cmd("AddNew=")
lt0.Cmd("\Q")
lt0.DbSet("Property[@Last]", "Name", propname)
return 0
op="myMirror"
AddNewProperty(op)
key="PROPERTY[" + op + "]"
lt0.DbSet(key,"Simple Type","Mirror")
Out[3]:
In [6]:
mirrorname="myMirror"
js.MakeTube(0.25,10,10,"R",mirrorname)
key="SOLID[@Last].SURFACE[LeftSurface].ZONE[1]"
lt0.DbSet(key,"PropertyName",op)
#Set the orientation, Alpha=45
key="Solid[@Last]"
lt0.DbSet(key,"Alpha",-45)
Out[6]:
In [7]:
#Add a NSRay
lt0.Cmd("NSRayAim xyz 0,10,0 xyz 0,0,0")
#Add a dummy plane
lt0.Cmd("DummyPlane xyz 0,0,-20 xyz 0,0,-40")
Out[7]:
In [8]:
key="Solid[1]"
segkey="NS_RAY[@Last].NS_SEGMENT[segment_2]"
numpts=11
datax=np.zeros((numpts,numpts))
datay=np.zeros((numpts,numpts))
alpha=np.linspace(-55,-35,11)
beta=np.linspace(-20,20,numpts)
for i in range(0,numpts,1):
lt0.DbSet(key,"Alpha",float(alpha[i]))
for j in range(0,11,1):
lt0.DbSet(key,"Beta",float(beta[j]))
datax[i,j]=lt0.DbGet(segkey,"Local_Surface_X")
datay[i,j]=lt0.DbGet(segkey,"Local_Surface_Y")
plt.scatter(datax,datay)
plt.xlabel('X')
plt.ylabel('Y')
Out[8]:
In [9]:
from scipy.optimize import minimize
import numpy as np
import matplotlib.pyplot as plt
import clr
#Initiate the connection with LightTools
clr.AddReference("C:\\Program Files\\Optical Research Associates\\LightTools 8.4.0\\Utilities.NET\\LTCOM64.dll")
from LTCOM64 import LTAPIx
lt0=LTAPIx()
lt0.UpdateLTPointer
Out[9]:
In [10]:
def EvalMF():
lt0.Cmd("\O" + lt0.Str("OPT_MERITFUNCTIONS[1]"))
lt0.Cmd("EvaluateAll=")
lt0.Cmd("\Q")
return 0
In [11]:
def setVarVals(v):
v=np.asarray(v)
vlist=lt0.DbList('Lens_Manager[1]','Opt_DBVariable')
vcount=lt0.ListSize(vlist)
lt0.SetOption('DbUpdate',0)
for i in range(1,vcount+1):
vkey=lt0.ListAtPos(vlist,i)
lt0.DbSet(vkey,'CurrentValue',float(v[i-1]))
print('Variable Value: ' + str(v[i-1]))
lt0.SetOption('DbUpdate',1)
lt0.ListDelete(vlist)
In [12]:
def ApplyVarsReturnMF(vardata):
myd=np.asarray(vardata)
setVarVals(myd)
EvalMF()
mfv=lt0.DbGet('OPT_MERITFUNCTIONS[1]','CurrentValue')
print("MF Value: " + str(mfv))
print('****')
return mfv
In [ ]:
# Here's a sample list of optimization algorithms we can try
# Some of these algorithms require 'jac', which is the Jacobian (gradiant), and it's not shown here
# The Nelder-Mead is the best option to try first, given its simplicity
optengines=['Nelder-Mead','BFGS','powell','Newton-CG','SLSQP','TNC']
vlist=lt0.DbList('Lens_Manager[1]','Opt_DBVariable')
vcount=int(lt0.ListSize(vlist))
lt0.ListDelete(vlist)
v0=np.zeros((vcount))
for i in range(1,vcount+1):
v0[i-1]=lt0.DbGet('OPT_DBVARIABLE[' +str(i) +']','CurrentValue')
# Note that 'maxiter' should be small (e.g. 5) for other algorithms, except 'Nelder-Mead'
res=minimize(ApplyVarsReturnMF,v0,method=optengines[0],options={'disp': True,'maxiter':50})
In [ ]:
res=minimize(ApplyVarsReturnMF,v0,method=optengines[2],options={'disp': True,'maxiter':5})
In [6]:
#Import the module and update the LT pointer
import LTData as ltd
ltd.lt0=lt0 #update the pointer
In [9]:
#Now you can get/set the data items like this
R = ltd.GetLTDbItem('Solid[1].Primitive[1].radius')
print('Radius is: ' + str(R))
ltd.SetLTDbItem('solid[1].primitive[1].radius',15)
illum=ltd.GetLTGridItem('receiver[1].mesh[1].CellValue_UI',45,45) #Accessing a 2D grid
print('Value is: ' + str(illum))
wave=ltd.GetLTGridItem('RECEIVER[1].SPECTRAL_DISTRIBUTION[1].Wavelength_At',5) #Accessing a 1D grid
print('Wavelength is: ' + str(wave))
#Make sure there's a valid spectral region with at least 1 row for the following code!
stat=ltd.SetLTGridItem('spectral_region[1].WavelengthAt',600,1) #Setting data in a 1D grid
In [10]:
#First, import standard libraries we need for arrays/plotting
import matplotlib.pyplot as plt # general plotting
import numpy as np #additional support for arrays, etc.
#Plot a mesh
ltd.PlotRaster('receiver[1].mesh[1]','cellvalue',colormap='jet',
xlabel='X-Value',ylabel='Y-Value',title='Mesh Data',plotsize=(5,5),plottype='2D')
Out[10]:
In [9]:
#Plot the spectral distribution
numrows,spd=ltd.PlotSpectralDistribution('receiver[1].spectral_distribution[1]',returndata=True)
plt.plot(spd[:,0],spd[:,1])
Out[9]:
In [11]:
#Plot true color data. Note the index=2 for the CIE mesh
r,g,b=ltd.PlotTrueColorRster('receiver[1].mesh[2]',plotsize=(5,5),returndata=True)
In [5]:
#We need to save the screenshot as an image file in the work directory
#LTUtilities module handles the work directory and file IO
import LTUtilities as ltu
ltu.lt0=lt0
ltd.ltu=ltu
#check the workdir
wd=ltu.checkpyWorkDir()
print(ltu.workdirstr) # this is where image files are saved
In [13]:
#Get a screenshot of the 3D View
viewname='3d'
im,imname=ltd.GetViewImage(viewname)
plt.imshow(im)
Out[13]:
In [15]:
#Get a screenshot of an open chart view
#Usually, V3D is the first view. The '3' below indicates the second chart view currently open
viewname='3'
im,imname=ltd.GetViewImage(viewname)
plt.imshow(im)
Out[15]:
In [16]:
#Let's get a screenshot of the full system
viewname='1'
im,imname=ltd.GetViewImage(viewname)
plt.imshow(im)
Out[16]:
In [7]:
#Ray path data
key='receiver[1]'
#First, let's hide all ray paths
lt0.Cmd('\O"RECEIVER[1].FORWARD_SIM_FUNCTION[1]" HideAll= \Q')
#Now get the ray path data, and show only the matchine paths
va,pa,ra,st=ltd.GetRayPathData(key,usevisibleonly=False)
# Two subplots, different size
from matplotlib import gridspec
fig = plt.figure(figsize=(6, 6))
gs = gridspec.GridSpec(2,1, height_ratios=[1,3])
ax1 = plt.subplot(gs[0])
ax1.plot(pa,'o')
ax1.set_xlabel('Path Index')
ax1.set_ylabel('Power')
ax1.grid(True)
s2='cylin' #this is the string we're searching for
for i in range(0,len(st)):
#print(st[i])
s1=st[i].lower()
if s2 in s1:
#print(str(i) + ';' + st[i])
ltd.SetLTGridItem(key + '.forward_sim_function[1].RayPathVisibleAt','yes',(i+1))
#Finally, let's get another screenshot to show the results
viewname='1'
im,imname=ltd.GetViewImage(viewname)
ax2 = plt.subplot(gs[1])
ax2.imshow(im)
ax2.axis('off')
plt.tight_layout()
In [31]:
#receiver ray data
des=['raydatax','raydatay','raydataz']
reckey='receiver[1]'
simtype='Forward_Sim_Function[1]'
#Note here that we specify the following function to
# use passfilters flag
N,M,raydata=ltd.GetLTReceiverRays(reckey,des,usepassfilters=True)
plt.plot(raydata[:,0],raydata[:,1],'o')
plt.xlabel('Ray Data Local X')
plt.ylabel('Ray Data Local Y')
plt.axis('equal')
Out[31]:
In [6]:
#Assume default data, x, y, z, l, m, n, p
simdata='forward_sim_function[1]'
reckey1='receiver[1]' #receiver on the lens surface
reckey2='receiver[2]' #receiver on the dummy plane
n,rayfname=ltd.MakeRayFileUsingRayOrdinal(reckey1,DataAccessKey_Ordinal=reckey2)
In [8]:
#Extra ray data, OPL
reckey='receiver[1]'
#Notice that the second argument is an Enum (integer) for the filter type
N,exdata=ltd.GetLTReceiverRays_Extra(reckey,ltd.ExtraRayData.Optical_Path_Length.value)
plt.hist(exdata,bins=21,color='green')
plt.xlabel('OPL')
plt.ylabel('Frequency')
Out[8]:
In [ ]:
import win32com.client
import numpy as np
import matplotlib.pyplot as plt
#DbGet() and Mesh data example
lt = win32com.client.Dispatch("LightTools.LTAPI4")
XD=int(lt.DbGet(MeshKey,"X_Dimension"))
YD=int(lt.DbGet(MeshKey,"Y_Dimension"))
k=np.ones((XD,YD))
#The CellFilter may not work for all options in COM mode
[stat,myd,f]=lt.GetMeshData("receiver[1].Mesh[1]",list(k),"CellValue")
g=np.asarray(myd)
g=np.rot90(g)
x = np.linspace(-3, 3, XD)
y = np.linspace(-3, 3, XD)
X,Y = np.meshgrid(x, y)
plt.pcolor(X,Y,g)
plt.pcolormesh(X,Y,g,cmap="gray")
plt.xlabel("X")
plt.ylabel("Y")
#JumpStart library
js = win32com.client.Dispatch("LTCOM64.JSML")
js.MakeSphere(lt,5,"mySphere")
js.MoveVector(lt,"mySphere",0,10,10)
# js.MoveVector(lt,"mys*",0,10,10) will move all objects whose name starts with 'mys'