├── .gitignore ├── Doc └── images │ ├── logo.png │ ├── pyzos_banner_large.png │ └── pyzos_banner_small.png ├── Examples └── jupyter_notebooks │ ├── 00_Enhancing_the_ZOS_API_Interface.ipynb │ ├── 01_Test_PyZOS_sync_ui_functionality.ipynb │ ├── 02_KB_article_How_to_create_a_User_Analysis_using_ZOS_API.ipynb │ └── images │ ├── 00_01_property_attribute.png │ ├── 00_02_constants.png │ ├── 00_03_extendiblity_custom_functions.png │ └── 00_04_extendiblity_required_methods.png ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── pyzos ├── __init__.py ├── ddeclient.py ├── zos.py ├── zos_obj_override │ ├── __init__.py │ ├── i_analyses_methods.py │ ├── ia__methods.py │ ├── iar__methods.py │ ├── ifields_methods.py │ ├── ilderow_methods.py │ ├── ilensdataeditor_methods.py │ ├── ilocaloptimization_methods.py │ ├── imeritfunctioneditor_methods.py │ ├── iopticalsystemtools_methods.py │ ├── isystemdata_methods.py │ └── izosapi_application_methods.py └── zosutils.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | ################# 32 | ## PyScripter 33 | ################# 34 | *.psproj 35 | 36 | ################# 37 | ## Spyder 38 | ################# 39 | .spyderworkspace 40 | .spyderproject 41 | 42 | ################# 43 | ## PyCharm 44 | ################# 45 | .idea/ 46 | 47 | ############################### 48 | ## Visual Studio/ PTVS projects 49 | ############################### 50 | 51 | ## Ignore Visual Studio temporary files, build results, and 52 | ## files generated by popular Visual Studio add-ons. 53 | 54 | *.pyproj 55 | *.suo 56 | *.sln 57 | *.filters 58 | *.userprefs 59 | *.vcxproj 60 | 61 | # User-specific files 62 | *.user 63 | *.userosscache 64 | *.sln.docstates 65 | 66 | # Build results 67 | [Dd]ebug/ 68 | *_i.c 69 | *_p.c 70 | *.ilk 71 | *.meta 72 | *.obj 73 | *.exp 74 | *.pch 75 | *.pdb 76 | *.pgc 77 | *.pgd 78 | *.rsp 79 | *.sbr 80 | *.tlb 81 | *.tli 82 | *.tlh 83 | *.tmp 84 | *.vspscc 85 | .builds 86 | *.dotCover 87 | *.log 88 | *.scc 89 | *.svclog 90 | *.tlog 91 | 92 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 93 | #packages/ 94 | 95 | # Visual C++ cache files 96 | ipch/ 97 | *.aps 98 | *.ncb 99 | *.opensdf 100 | *.sdf 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | 106 | # ReSharper is a .NET coding add-in 107 | _ReSharper* 108 | 109 | # Installshield output folder 110 | [Ee]xpress 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish 124 | 125 | # Others 126 | [Bb]in 127 | [Oo]bj 128 | sql 129 | TestResults 130 | *.Cache 131 | ClientBin 132 | stylecop.* 133 | ~$* 134 | *.dbmdl 135 | 136 | Generated_Code #added for RIA/Silverlight projects 137 | 138 | # Backup & report files from converting an old project file to a newer 139 | # Visual Studio version. Backup files are not needed, because we have git ;-) 140 | _UpgradeReport_Files/ 141 | Backup*/ 142 | UpgradeLog*.XML 143 | 144 | 145 | 146 | ############ 147 | ## Windows 148 | ############ 149 | 150 | # Windows image file caches 151 | Thumbs.db 152 | 153 | # Folder config file 154 | Desktop.ini 155 | 156 | 157 | ############# 158 | ## Python 159 | ############# 160 | 161 | *.py[co] 162 | 163 | # Packages 164 | *.egg 165 | *.egg-info 166 | dist 167 | build 168 | eggs 169 | parts 170 | bin 171 | var 172 | sdist 173 | develop-eggs 174 | .installed.cfg 175 | 176 | ########### 177 | # IPython 178 | ########### 179 | .ipynb_checkpoints/ 180 | 181 | # Installer logs 182 | pip-log.txt 183 | 184 | # Unit test / coverage reports 185 | .coverage 186 | .tox 187 | 188 | # Translations 189 | *.mo 190 | 191 | # Mr Developer 192 | .mr.developer.cfg 193 | 194 | 195 | 196 | ########################## 197 | ## Project specific files 198 | ########################## 199 | *.ini 200 | 201 | 202 | # ZEMAX settings files and sessions files 203 | *.CFG 204 | *.SES 205 | *.ZMX 206 | *.zmx 207 | 208 | # Files that needs to be synced on/off sometimes 209 | *.dll 210 | -------------------------------------------------------------------------------- /Doc/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzos/pyzos/da6bf3296b0154ccee44ad9a4286055ae031ecc7/Doc/images/logo.png -------------------------------------------------------------------------------- /Doc/images/pyzos_banner_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzos/pyzos/da6bf3296b0154ccee44ad9a4286055ae031ecc7/Doc/images/pyzos_banner_large.png -------------------------------------------------------------------------------- /Doc/images/pyzos_banner_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzos/pyzos/da6bf3296b0154ccee44ad9a4286055ae031ecc7/Doc/images/pyzos_banner_small.png -------------------------------------------------------------------------------- /Examples/jupyter_notebooks/01_Test_PyZOS_sync_ui_functionality.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Test Sync_UI functionality of PyZOS" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": { 14 | "collapsed": true 15 | }, 16 | "outputs": [], 17 | "source": [ 18 | "import os\n", 19 | "import pyzos.zos as zos\n", 20 | "import warnings\n", 21 | "warnings.simplefilter(action='ignore')" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "## Test zPushLens() " 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": {}, 34 | "source": [ 35 | "Build a basic optical system and Push to the UI" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 2, 41 | "metadata": { 42 | "collapsed": true 43 | }, 44 | "outputs": [], 45 | "source": [ 46 | "# Create an OpticalSystem instance with sync_ui parameter set to True\n", 47 | "\n", 48 | "osys = zos.OpticalSystem(sync_ui=True)" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 3, 54 | "metadata": { 55 | "collapsed": false 56 | }, 57 | "outputs": [], 58 | "source": [ 59 | "# Set aperture Aperture\n", 60 | "sdata = osys.pSystemData\n", 61 | "sdata.pAperture.pApertureValue = 40\n", 62 | "\n", 63 | "# Set fields\n", 64 | "field = sdata.pFields.AddField(0, 5.0, 1.0)\n", 65 | "\n", 66 | "# Set wavelength\n", 67 | "sdata.pWavelengths.SelectWavelengthPreset(zos.Const.WavelengthPreset_d_0p587);" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": 4, 73 | "metadata": { 74 | "collapsed": true 75 | }, 76 | "outputs": [], 77 | "source": [ 78 | "# Create surfaces\n", 79 | "osys.zInsertNewSurfaceAt(1)\n", 80 | "osys.zInsertNewSurfaceAt(1)\n", 81 | "osys.zSetSurfaceData(1, thick=10, material='N-BK7', comment='front of lens')\n", 82 | "osys.zSetSurfaceData(2, thick=50, comment='rear of lens')\n", 83 | "osys.zSetSurfaceData(3, thick=350, comment='Stop is free to move')" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 5, 89 | "metadata": { 90 | "collapsed": false 91 | }, 92 | "outputs": [], 93 | "source": [ 94 | "osys.zPushLens(update=1) # with 1, the lens data will be updated in the UI" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 6, 100 | "metadata": { 101 | "collapsed": false 102 | }, 103 | "outputs": [ 104 | { 105 | "data": { 106 | "text/plain": [ 107 | "True" 108 | ] 109 | }, 110 | "execution_count": 6, 111 | "metadata": {}, 112 | "output_type": "execute_result" 113 | } 114 | ], 115 | "source": [ 116 | "# Set solves on surfaces\n", 117 | "osys.pLDE.GetSurfaceAt(1).pRadiusCell.MakeSolveVariable()\n", 118 | "osys.pLDE.GetSurfaceAt(1).pThicknessCell.MakeSolveVariable()\n", 119 | "osys.pLDE.GetSurfaceAt(2).pRadiusCell.MakeSolveVariable()\n", 120 | "osys.pLDE.GetSurfaceAt(2).pThicknessCell.MakeSolveVariable()\n", 121 | "osys.pLDE.GetSurfaceAt(3).pThicknessCell.MakeSolveVariable()" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 7, 127 | "metadata": { 128 | "collapsed": false 129 | }, 130 | "outputs": [], 131 | "source": [ 132 | "osys.zSetDefaultMeritFunctionSEQ(ofType=0, ofData=1, ofRef=0, rings=2, \n", 133 | " arms=0, grid=0, useGlass=True, glassMin=3, \n", 134 | " glassMax=15, glassEdge=3, useAir=True, \n", 135 | " airMin=0.5, airMax=1000, airEdge=0.5)" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": 8, 141 | "metadata": { 142 | "collapsed": false 143 | }, 144 | "outputs": [], 145 | "source": [ 146 | "osys.zPushLens(1)" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": 9, 152 | "metadata": { 153 | "collapsed": false 154 | }, 155 | "outputs": [], 156 | "source": [ 157 | "# Additional Operand\n", 158 | "mfe = osys.pMFE\n", 159 | "operand1 = mfe.InsertNewOperandAt(1)\n", 160 | "operand1 = zos.wrapped_zos_object(operand1)\n", 161 | "operand1.ChangeType(zos.Const.MeritOperandType_EFFL)\n", 162 | "operand1.pTarget = 400.0\n", 163 | "operand1.pWeight = 1.0" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": 10, 169 | "metadata": { 170 | "collapsed": false 171 | }, 172 | "outputs": [], 173 | "source": [ 174 | "osys.zPushLens(1)" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": 11, 180 | "metadata": { 181 | "collapsed": false 182 | }, 183 | "outputs": [ 184 | { 185 | "data": { 186 | "text/plain": [ 187 | "True" 188 | ] 189 | }, 190 | "execution_count": 11, 191 | "metadata": {}, 192 | "output_type": "execute_result" 193 | } 194 | ], 195 | "source": [ 196 | "# Local optimization\n", 197 | "local_opt = osys.pTools.OpenLocalOptimization()\n", 198 | "local_opt.pAlgorithm = zos.Const.OptimizationAlgorithm_DampedLeastSquares\n", 199 | "local_opt.pCycles = zos.Const.OptimizationCycles_Automatic\n", 200 | "local_opt.pNumberOfCores = 8\n", 201 | "local_opt.RunAndWaitForCompletion()\n", 202 | "local_opt.Close()" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": 12, 208 | "metadata": { 209 | "collapsed": false 210 | }, 211 | "outputs": [], 212 | "source": [ 213 | "osys.zPushLens(1)" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": 13, 219 | "metadata": { 220 | "collapsed": false 221 | }, 222 | "outputs": [], 223 | "source": [ 224 | "# Save lens file (optional)\n", 225 | "sdir = osys.pTheApplication.pSamplesDir\n", 226 | "file_out = os.path.join(sdir, 'Sequential', 'Objectives', \n", 227 | " 'Single Lens Example wizard+EFFL.zmx')\n", 228 | "osys.SaveAs(file_out)" 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "execution_count": null, 234 | "metadata": { 235 | "collapsed": true 236 | }, 237 | "outputs": [], 238 | "source": [] 239 | }, 240 | { 241 | "cell_type": "markdown", 242 | "metadata": {}, 243 | "source": [ 244 | "## Test zGetRefresh()" 245 | ] 246 | }, 247 | { 248 | "cell_type": "code", 249 | "execution_count": 14, 250 | "metadata": { 251 | "collapsed": false 252 | }, 253 | "outputs": [], 254 | "source": [ 255 | "# LDE\n", 256 | "lde = osys.pLDE" 257 | ] 258 | }, 259 | { 260 | "cell_type": "markdown", 261 | "metadata": {}, 262 | "source": [ 263 | "We will change the thickness of surface 2 in the UI." 264 | ] 265 | }, 266 | { 267 | "cell_type": "code", 268 | "execution_count": 15, 269 | "metadata": { 270 | "collapsed": true 271 | }, 272 | "outputs": [], 273 | "source": [ 274 | "# Get row 2\n", 275 | "row2 = lde.GetRowAt(2)" 276 | ] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "execution_count": 16, 281 | "metadata": { 282 | "collapsed": false 283 | }, 284 | "outputs": [ 285 | { 286 | "data": { 287 | "text/plain": [ 288 | "94.10279977269181" 289 | ] 290 | }, 291 | "execution_count": 16, 292 | "metadata": {}, 293 | "output_type": "execute_result" 294 | } 295 | ], 296 | "source": [ 297 | "thickness2 = zos.wrapped_zos_object(row2.GetCellAt(3)).pValue\n", 298 | "float(thickness2)" 299 | ] 300 | }, 301 | { 302 | "cell_type": "markdown", 303 | "metadata": {}, 304 | "source": [ 305 | "Change the thickness value in the UI manually." 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": 17, 311 | "metadata": { 312 | "collapsed": false 313 | }, 314 | "outputs": [], 315 | "source": [ 316 | "osys.zGetRefresh() # zGetRefresh to update the lens in the COM server" 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": 18, 322 | "metadata": { 323 | "collapsed": false 324 | }, 325 | "outputs": [ 326 | { 327 | "data": { 328 | "text/plain": [ 329 | "150.0" 330 | ] 331 | }, 332 | "execution_count": 18, 333 | "metadata": {}, 334 | "output_type": "execute_result" 335 | } 336 | ], 337 | "source": [ 338 | "thickness2 = zos.wrapped_zos_object(row2.GetCellAt(3)).pValue\n", 339 | "float(thickness2)" 340 | ] 341 | }, 342 | { 343 | "cell_type": "code", 344 | "execution_count": 19, 345 | "metadata": { 346 | "collapsed": false 347 | }, 348 | "outputs": [], 349 | "source": [ 350 | "osys.New(False)" 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": 20, 356 | "metadata": { 357 | "collapsed": true 358 | }, 359 | "outputs": [], 360 | "source": [ 361 | "osys.zPushLens(1)" 362 | ] 363 | }, 364 | { 365 | "cell_type": "code", 366 | "execution_count": null, 367 | "metadata": { 368 | "collapsed": true 369 | }, 370 | "outputs": [], 371 | "source": [] 372 | } 373 | ], 374 | "metadata": { 375 | "kernelspec": { 376 | "display_name": "Python 2", 377 | "language": "python", 378 | "name": "python2" 379 | }, 380 | "language_info": { 381 | "codemirror_mode": { 382 | "name": "ipython", 383 | "version": 2 384 | }, 385 | "file_extension": ".py", 386 | "mimetype": "text/x-python", 387 | "name": "python", 388 | "nbconvert_exporter": "python", 389 | "pygments_lexer": "ipython2", 390 | "version": "2.7.11" 391 | } 392 | }, 393 | "nbformat": 4, 394 | "nbformat_minor": 0 395 | } 396 | -------------------------------------------------------------------------------- /Examples/jupyter_notebooks/02_KB_article_How_to_create_a_User_Analysis_using_ZOS_API.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# KB article \"How to create a User Analysis using ZOS-API\"" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Source: \"[How to create a User Analysis using ZOS-API](http://www.zemax.com/support/resource-center/knowledgebase/how-to-create-a-user-analysis-using-zos-api),\" Thomas Aumeyr, Nov. 2015" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": { 21 | "collapsed": true 22 | }, 23 | "outputs": [], 24 | "source": [ 25 | "from __future__ import print_function, division\n", 26 | "import os\n", 27 | "import numpy as np\n", 28 | "import matplotlib.pyplot as plt\n", 29 | "import pyzos.zos as zos" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 2, 35 | "metadata": { 36 | "collapsed": true 37 | }, 38 | "outputs": [], 39 | "source": [ 40 | "%matplotlib inline" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": 3, 46 | "metadata": { 47 | "collapsed": true 48 | }, 49 | "outputs": [], 50 | "source": [ 51 | "# instantiate an optical system\n", 52 | "osys = zos.OpticalSystem()" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 4, 58 | "metadata": { 59 | "collapsed": false 60 | }, 61 | "outputs": [ 62 | { 63 | "data": { 64 | "text/plain": [ 65 | "True" 66 | ] 67 | }, 68 | "execution_count": 4, 69 | "metadata": {}, 70 | "output_type": "execute_result" 71 | } 72 | ], 73 | "source": [ 74 | "# Load the zemax lens file into the optical system\n", 75 | "sdir = osys.pTheApplication.pSamplesDir\n", 76 | "lens = 'Double Gauss 28 degree field.zmx'\n", 77 | "lensfile = os.path.join(sdir, 'Sequential', 'Objectives', lens)\n", 78 | "osys.LoadFile(lensfile, False)" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 5, 84 | "metadata": { 85 | "collapsed": false 86 | }, 87 | "outputs": [], 88 | "source": [ 89 | "# editors\n", 90 | "lde = osys.pLDE\n", 91 | "mfe = osys.pMFE" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 6, 97 | "metadata": { 98 | "collapsed": true 99 | }, 100 | "outputs": [], 101 | "source": [ 102 | "# add 3 operands to MFE\n", 103 | "operands = []\n", 104 | "for i in range(3):\n", 105 | " operands.append(mfe.AddOperand())" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": 7, 111 | "metadata": { 112 | "collapsed": false 113 | }, 114 | "outputs": [], 115 | "source": [ 116 | "# set operand parameters\n", 117 | "frequencies = [30, 40, 50]\n", 118 | "for oper, freq in zip(operands, frequencies):\n", 119 | " _ = oper.ChangeType(zos.Const.MeritOperandType_MTFS)\n", 120 | " oper_samp = oper.GetOperandCell(zos.Const.MeritColumn_Param1)\n", 121 | " oper_samp.pIntegerValue = 2\n", 122 | " oper_freq = oper.GetOperandCell(zos.Const.MeritColumn_Param4)\n", 123 | " oper_freq.pDoubleValue = freq" 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "metadata": {}, 129 | "source": [ 130 | "Within the loop, we will vary the thickness of the surface 6 and run QuickFocus before computing the MTF." 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": 8, 136 | "metadata": { 137 | "collapsed": false 138 | }, 139 | "outputs": [ 140 | { 141 | "data": { 142 | "text/plain": [ 143 | "True" 144 | ] 145 | }, 146 | "execution_count": 8, 147 | "metadata": {}, 148 | "output_type": "execute_result" 149 | } 150 | ], 151 | "source": [ 152 | "# loop to get the MTF values as a function of thickness of surf 6\n", 153 | "mtfs30 = []\n", 154 | "mtfs40 = []\n", 155 | "mtfs50 = []\n", 156 | "surf6 = lde.GetSurfaceAt(6)\n", 157 | "thickness = surf6.pThickness + np.linspace(-1, 1, 201)\n", 158 | "quick_focus = osys.pTools.OpenQuickFocus()\n", 159 | "quick_focus.pUseCentroid = True\n", 160 | "\n", 161 | "for thick in thickness:\n", 162 | " surf6.pThickness = thick\n", 163 | " quick_focus.RunAndWaitForCompletion()\n", 164 | " mfe.CalculateMeritFunction()\n", 165 | " mtfs30.append(operands[0].pValue)\n", 166 | " mtfs40.append(operands[1].pValue)\n", 167 | " mtfs50.append(operands[2].pValue)\n", 168 | " \n", 169 | "quick_focus.Close()" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 9, 175 | "metadata": { 176 | "collapsed": false 177 | }, 178 | "outputs": [ 179 | { 180 | "data": { 181 | "image/png": "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\n", 182 | "text/plain": [ 183 | "" 184 | ] 185 | }, 186 | "metadata": {}, 187 | "output_type": "display_data" 188 | } 189 | ], 190 | "source": [ 191 | "# plot\n", 192 | "fig, ax = plt.subplots(1,1, figsize=(9, 6))\n", 193 | "ax.plot(thickness, np.array(mtfs30), label='MTF 30 cycles/mm')\n", 194 | "ax.plot(thickness, np.array(mtfs40), label='MTF 40 cycles/mm')\n", 195 | "ax.plot(thickness, np.array(mtfs50), label='MTF 50 cycles/mm')\n", 196 | "ax.set_xlabel('Surface 6 Thickness [mm]')\n", 197 | "ax.set_ylabel('MTF')\n", 198 | "ax.set_xlim(thickness[0], thickness[-1])\n", 199 | "ax.set_ylim(0, 1)\n", 200 | "ax.grid()\n", 201 | "ax.legend()\n", 202 | "plt.show()" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": 10, 208 | "metadata": { 209 | "collapsed": false 210 | }, 211 | "outputs": [], 212 | "source": [ 213 | "osys.Close(False)\n", 214 | "osys.pTheApplication.CloseApplication()" 215 | ] 216 | }, 217 | { 218 | "cell_type": "code", 219 | "execution_count": null, 220 | "metadata": { 221 | "collapsed": true 222 | }, 223 | "outputs": [], 224 | "source": [] 225 | } 226 | ], 227 | "metadata": { 228 | "kernelspec": { 229 | "display_name": "Python 2", 230 | "language": "python", 231 | "name": "python2" 232 | }, 233 | "language_info": { 234 | "codemirror_mode": { 235 | "name": "ipython", 236 | "version": 2 237 | }, 238 | "file_extension": ".py", 239 | "mimetype": "text/x-python", 240 | "name": "python", 241 | "nbconvert_exporter": "python", 242 | "pygments_lexer": "ipython2", 243 | "version": "2.7.11" 244 | } 245 | }, 246 | "nbformat": 4, 247 | "nbformat_minor": 0 248 | } 249 | -------------------------------------------------------------------------------- /Examples/jupyter_notebooks/images/00_01_property_attribute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzos/pyzos/da6bf3296b0154ccee44ad9a4286055ae031ecc7/Examples/jupyter_notebooks/images/00_01_property_attribute.png -------------------------------------------------------------------------------- /Examples/jupyter_notebooks/images/00_02_constants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzos/pyzos/da6bf3296b0154ccee44ad9a4286055ae031ecc7/Examples/jupyter_notebooks/images/00_02_constants.png -------------------------------------------------------------------------------- /Examples/jupyter_notebooks/images/00_03_extendiblity_custom_functions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzos/pyzos/da6bf3296b0154ccee44ad9a4286055ae031ecc7/Examples/jupyter_notebooks/images/00_03_extendiblity_custom_functions.png -------------------------------------------------------------------------------- /Examples/jupyter_notebooks/images/00_04_extendiblity_required_methods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzos/pyzos/da6bf3296b0154ccee44ad9a4286055ae031ecc7/Examples/jupyter_notebooks/images/00_04_extendiblity_required_methods.png -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 PyZOS Development Team. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt 2 | include README.rst 3 | 4 | recursive-include Doc *.rst *.html *.ipynb 5 | recursive-include pyzos/zos_obj_override *.py 6 | recursive-include Examples/scripts *.py *.zbf *.png *.txt *.CFG 7 | recursive-include Examples/misc *.py 8 | recursive-include Examples/jupyter_notebooks *.ipynb 9 | prune pyzos/__pycache__ 10 | prune Examples/jupyter_notebooks/.ipynb_checkpoints -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://raw.githubusercontent.com/pyzos/pyzos/master/Doc/images/pyzos_banner_small.png 2 | 3 | Python Zemax OpticStudio API 4 | ---------------------------- 5 | 6 | Current revision 7 | '''''''''''''''' 8 | 0.0.8 (Last significant update on 02/26/2016) 9 | 10 | Philosophy / Design Goals 11 | ''''''''''''''''''''''''' 12 | 13 | **Please note** that the first 3 problems stated below arises only if the ZOS-API's COM interface is used with `PyWin32 `__. 14 | An alternative, and probably better, way to use the .NET interface of ZOS-API is through the `Python for .NET `__. 15 | 16 | Problems 17 | ~~~~~~~~ 18 | 19 | The ZOS-API is an excellent interface for OpticStudio. However, using the ZOS COM API in 20 | Python directly through PyWin32 is not conducive and feels very unpythonic for the following 21 | reasons: 22 | 23 | * the large set of *property* attributes of the ZOS objects are not introspectable, 24 | * several interface objects require appropriate type casting before use, 25 | * the interface is quite complex (albeit flexible) requiring a significant amount of coding, 26 | * the ZOS-API only works in standalone (headless) mode. This prevents users from interacting with a 27 | running OpticStudio user-interface and observing changes made to the design instantly. 28 | 29 | Solutions 30 | ~~~~~~~~~ 31 | 32 | The philosophy behind PyZOS is to make ZOS-API easier to use in Python by: 33 | 34 | * enabling interactivity with a running OpticStudio user-interface (`see demo `__) 35 | * providing better introspection of objects' properties and methods 36 | * reducing complexity by 37 | 38 | - providing a set of custom helper-methods that accomplishes common tasks in single or fewest possible steps 39 | - providing a framework for easily adding custom methods that seamlessly couple with existing ZOS objects 40 | - managing appropriate type casting of ZOS objects so that we can concentrate on solving optical design problem 41 | 42 | * do all the above without limiting or obscuring the ZOS-API in any way 43 | * do all the above with as minimum coding as possible (i.e. do a lot by doing very little) 44 | 45 | These *enhancements* to ZOS-API using PyZOS library are documented in this (work in progress) 46 | `Jupyter notebook `__. 47 | 48 | 49 | 50 | Example usage 51 | ''''''''''''' 52 | .. code:: python 53 | 54 | import pyzos.zos as zos 55 | osys = zos.OpticalSystem(sync_ui=True) # enable interactivity with a running UI 56 | sdata = osys.pSystemData 57 | sdata.pAperture.pApertureValue = 40 58 | sdata.pFields.AddField(0, 2.0, 1.0) 59 | wave = zos.Const.WavelengthPreset_d_0p587 60 | sdata.pWavelengths.SelectWavelengthPreset(wave) 61 | ... 62 | osys.zPushLens(1) # copy lens from ZOS COM server to the visible UI app 63 | ... 64 | osys.zGetRefresh() # copy changes from the visible UI app to the ZOS COM server 65 | ... 66 | 67 | See more example use of PyZOS used within Jupyter notebooks `here `__. 68 | 69 | 70 | Install PyZOS from PyPI 71 | '''''''''''''''''''''''' 72 | 73 | Use the following command from the command line to install PyZOS from PyPI: 74 | 75 | .. code:: python 76 | 77 | pip install pyzos 78 | 79 | 80 | 81 | Interested in contributing? 82 | ''''''''''''''''''''''''''' 83 | You can contribute in may ways: 84 | 85 | 1. using the library and giving feedback, suggestions and reporting bugs 86 | 2. adding custom functions you wrote for your project that others can use 87 | 3. helping to write the unit-test, adding test cases 88 | 4. letting others know about PyZOS (if you found it useful) 89 | 90 | 91 | Dependencies 92 | '''''''''''' 93 | 94 | The core PyZOS library only depends on the standard Python Library. 95 | 96 | 1. Python 3.3 and above / Python 2.7; 32/64 bit version 97 | 2. `PyWin32 `__ 98 | 99 | All the dependencies can be installed by using the Anaconda Python distribution. 100 | 101 | License 102 | ''''''' 103 | 104 | The code is under the `MIT License `__. 105 | -------------------------------------------------------------------------------- /pyzos/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzos/pyzos/da6bf3296b0154ccee44ad9a4286055ae031ecc7/pyzos/__init__.py -------------------------------------------------------------------------------- /pyzos/ddeclient.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: ddeclient.py 4 | # Purpose: DDE Management Library (DDEML) client application for communicating 5 | # with Zemax 6 | # 7 | # Notes: This code has been adapted from David Naylor's dde-client code from 8 | # ActiveState's Python recipes (Revision 1). 9 | # Author of original Code: David Naylor, Apr 2011 10 | # Modified by Indranil Sinharoy 11 | # Copyright: (c) David Naylor 12 | # Licence: New BSD license (Please see the file Notice.txt for further details) 13 | # Website: http://code.activestate.com/recipes/577654-dde-client/ 14 | #------------------------------------------------------------------------------- 15 | from __future__ import print_function 16 | import sys 17 | from ctypes import c_int, c_double, c_char_p, c_void_p, c_ulong, c_char, pointer, cast 18 | from ctypes import windll, byref, create_string_buffer, Structure, sizeof 19 | from ctypes import POINTER, WINFUNCTYPE 20 | from ctypes.wintypes import BOOL, HWND, MSG, DWORD, BYTE, INT, LPCWSTR, UINT, ULONG, LPCSTR 21 | 22 | # DECLARE_HANDLE(name) typedef void *name; 23 | HCONV = c_void_p # = DECLARE_HANDLE(HCONV) 24 | HDDEDATA = c_void_p # = DECLARE_HANDLE(HDDEDATA) 25 | HSZ = c_void_p # = DECLARE_HANDLE(HSZ) 26 | LPBYTE = c_char_p # POINTER(BYTE) 27 | LPDWORD = POINTER(DWORD) 28 | LPSTR = c_char_p 29 | ULONG_PTR = c_ulong 30 | 31 | # See windows/ddeml.h for declaration of struct CONVCONTEXT 32 | PCONVCONTEXT = c_void_p 33 | 34 | # DDEML errors 35 | DMLERR_NO_ERROR = 0x0000 # No error 36 | DMLERR_ADVACKTIMEOUT = 0x4000 # request for synchronous advise transaction timed out 37 | DMLERR_DATAACKTIMEOUT = 0x4002 # request for synchronous data transaction timed out 38 | DMLERR_DLL_NOT_INITIALIZED = 0x4003 # DDEML functions called without iniatializing 39 | DMLERR_EXECACKTIMEOUT = 0x4006 # request for synchronous execute transaction timed out 40 | DMLERR_NO_CONV_ESTABLISHED = 0x400a # client's attempt to establish a conversation has failed (can happen during DdeConnect) 41 | DMLERR_POKEACKTIMEOUT = 0x400b # A request for a synchronous poke transaction has timed out. 42 | DMLERR_POSTMSG_FAILED = 0x400c # An internal call to the PostMessage function has failed. 43 | DMLERR_SERVER_DIED = 0x400e 44 | 45 | # Predefined Clipboard Formats 46 | CF_TEXT = 1 47 | CF_BITMAP = 2 48 | CF_METAFILEPICT = 3 49 | CF_SYLK = 4 50 | CF_DIF = 5 51 | CF_TIFF = 6 52 | CF_OEMTEXT = 7 53 | CF_DIB = 8 54 | CF_PALETTE = 9 55 | CF_PENDATA = 10 56 | CF_RIFF = 11 57 | CF_WAVE = 12 58 | CF_UNICODETEXT = 13 59 | CF_ENHMETAFILE = 14 60 | CF_HDROP = 15 61 | CF_LOCALE = 16 62 | CF_DIBV5 = 17 63 | CF_MAX = 18 64 | 65 | # DDE constants for wStatus field 66 | DDE_FACK = 0x8000 67 | DDE_FBUSY = 0x4000 68 | DDE_FDEFERUPD = 0x4000 69 | DDE_FACKREQ = 0x8000 70 | DDE_FRELEASE = 0x2000 71 | DDE_FREQUESTED = 0x1000 72 | DDE_FAPPSTATUS = 0x00FF 73 | DDE_FNOTPROCESSED = 0x0000 74 | 75 | DDE_FACKRESERVED = (~(DDE_FACK | DDE_FBUSY | DDE_FAPPSTATUS)) 76 | DDE_FADVRESERVED = (~(DDE_FACKREQ | DDE_FDEFERUPD)) 77 | DDE_FDATRESERVED = (~(DDE_FACKREQ | DDE_FRELEASE | DDE_FREQUESTED)) 78 | DDE_FPOKRESERVED = (~(DDE_FRELEASE)) 79 | 80 | # DDEML Transaction class flags 81 | XTYPF_NOBLOCK = 0x0002 82 | XTYPF_NODATA = 0x0004 83 | XTYPF_ACKREQ = 0x0008 84 | 85 | XCLASS_MASK = 0xFC00 86 | XCLASS_BOOL = 0x1000 87 | XCLASS_DATA = 0x2000 88 | XCLASS_FLAGS = 0x4000 89 | XCLASS_NOTIFICATION = 0x8000 90 | 91 | XTYP_ERROR = (0x0000 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK) 92 | XTYP_ADVDATA = (0x0010 | XCLASS_FLAGS) 93 | XTYP_ADVREQ = (0x0020 | XCLASS_DATA | XTYPF_NOBLOCK) 94 | XTYP_ADVSTART = (0x0030 | XCLASS_BOOL) 95 | XTYP_ADVSTOP = (0x0040 | XCLASS_NOTIFICATION) 96 | XTYP_EXECUTE = (0x0050 | XCLASS_FLAGS) 97 | XTYP_CONNECT = (0x0060 | XCLASS_BOOL | XTYPF_NOBLOCK) 98 | XTYP_CONNECT_CONFIRM = (0x0070 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK) 99 | XTYP_XACT_COMPLETE = (0x0080 | XCLASS_NOTIFICATION ) 100 | XTYP_POKE = (0x0090 | XCLASS_FLAGS) 101 | XTYP_REGISTER = (0x00A0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK ) 102 | XTYP_REQUEST = (0x00B0 | XCLASS_DATA ) 103 | XTYP_DISCONNECT = (0x00C0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK ) 104 | XTYP_UNREGISTER = (0x00D0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK ) 105 | XTYP_WILDCONNECT = (0x00E0 | XCLASS_DATA | XTYPF_NOBLOCK) 106 | XTYP_MONITOR = (0x00F0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK) 107 | 108 | XTYP_MASK = 0x00F0 109 | XTYP_SHIFT = 4 110 | 111 | # DDE Timeout constants 112 | TIMEOUT_ASYNC = 0xFFFFFFFF 113 | 114 | # DDE Application command flags / Initialization flag (afCmd) 115 | APPCMD_CLIENTONLY = 0x00000010 116 | 117 | # Code page for rendering string. 118 | CP_WINANSI = 1004 # default codepage for windows & old DDE convs. 119 | CP_WINUNICODE = 1200 120 | 121 | # Declaration 122 | DDECALLBACK = WINFUNCTYPE(HDDEDATA, UINT, UINT, HCONV, HSZ, HSZ, HDDEDATA, ULONG_PTR, ULONG_PTR) 123 | 124 | # PyZOS specific globals 125 | number_of_apps_communicating = 0 # to keep an account of the number of zemax 126 | # server objects --'ZEMAX', 'ZEMAX1' etc 127 | 128 | class CreateServer(object): 129 | """This is really just an interface class so that PyZDDE can use either the 130 | current dde code or the pywin32 transparently. This object is created only 131 | once. The class name cannot be anything else if compatibility has to be maintained 132 | between pywin32 and this dde code. 133 | """ 134 | def __init__(self): 135 | self.serverName = 'None' 136 | 137 | def Create(self, client): 138 | """Set a DDE client that will communicate with the DDE server 139 | 140 | Parameters 141 | ---------- 142 | client : string 143 | Name of the DDE client, most likely this will be 'ZCLIENT' 144 | """ 145 | self.clientName = client # shall be used in `CreateConversation` 146 | 147 | def Shutdown(self, createConvObj): 148 | """The shutdown should ideally be requested only once per CreateConversation 149 | object by the PyZDDE module, but for ALL CreateConversation objects, if there 150 | are more than one. If multiple CreateConversation objects were created and 151 | then not cleared, there will be memory leak, and eventually the program will 152 | error out when run multiple times 153 | 154 | Parameters 155 | ---------- 156 | createConvObj : CreateConversation object 157 | 158 | Exceptions 159 | ---------- 160 | An exception occurs if a Shutdown is attempted with a CreateConvObj that 161 | doesn't have a conversation object (connection with ZEMAX established) 162 | """ 163 | global number_of_apps_communicating 164 | #print("Shutdown requested by {}".format(repr(createConvObj))) # for debugging 165 | if number_of_apps_communicating > 0: 166 | #print("Deleting object ...") # for debugging 167 | createConvObj.ddec.__del__() 168 | number_of_apps_communicating -=1 169 | 170 | 171 | class CreateConversation(object): 172 | """This is really just an interface class so that PyZDDE can use either the 173 | current dde code or the pywin32 transparently. 174 | 175 | Multiple objects of this type may be instantiated depending upon the 176 | number of simultaneous channels of communication with Zemax that the user 177 | program wants to establish using `ln = pyz.PyZDDE()` followed by `ln.zDDEInit()` 178 | calls. 179 | """ 180 | def __init__(self, ddeServer): 181 | """ 182 | Parameters 183 | ---------- 184 | ddeServer : 185 | d 186 | """ 187 | self.ddeClientName = ddeServer.clientName 188 | self.ddeServerName = 'None' 189 | self.ddetimeout = 50 # default dde timeout = 50 seconds 190 | 191 | def ConnectTo(self, appName, data=None): 192 | """Exceptional error is handled in zdde Init() method, so the exception 193 | must be re-raised""" 194 | global number_of_apps_communicating 195 | self.ddeServerName = appName 196 | try: 197 | self.ddec = DDEClient(self.ddeServerName, self.ddeClientName) # establish conversation 198 | except DDEError: 199 | raise 200 | else: 201 | number_of_apps_communicating +=1 202 | #print("Number of apps communicating: ", number_of_apps_communicating) # for debugging 203 | 204 | def Request(self, item, timeout=None): 205 | """Request DDE client 206 | timeout in seconds 207 | Note ... handle the exception within this function. 208 | """ 209 | if not timeout: 210 | timeout = self.ddetimeout 211 | try: 212 | reply = self.ddec.request(item, int(timeout*1000)) # convert timeout into milliseconds 213 | except DDEError: 214 | err_str = str(sys.exc_info()[1]) 215 | error = err_str[err_str.find('err=')+4:err_str.find('err=')+10] 216 | if error == hex(DMLERR_DATAACKTIMEOUT): 217 | print("TIMEOUT REACHED. Please use a higher timeout.\n") 218 | if (sys.version_info > (3, 0)): #this is only evaluated in case of an error 219 | reply = b'-998' #Timeout error value 220 | else: 221 | reply = '-998' #Timeout error value 222 | return reply 223 | 224 | 225 | def SetDDETimeout(self, timeout): 226 | """Set DDE timeout 227 | timeout : timeout in seconds 228 | """ 229 | self.ddetimeout = timeout 230 | 231 | def GetDDETimeout(self): 232 | """Returns the current timeout value in seconds 233 | """ 234 | return self.ddetimeout 235 | 236 | 237 | def get_winfunc(libname, funcname, restype=None, argtypes=(), _libcache={}): 238 | """Retrieve a function from a library/DLL, and set the data types.""" 239 | if libname not in _libcache: 240 | _libcache[libname] = windll.LoadLibrary(libname) 241 | func = getattr(_libcache[libname], funcname) 242 | func.argtypes = argtypes 243 | func.restype = restype 244 | return func 245 | 246 | class DDE(object): 247 | """Object containing all the DDEML functions""" 248 | AccessData = get_winfunc("user32", "DdeAccessData", LPBYTE, (HDDEDATA, LPDWORD)) 249 | ClientTransaction = get_winfunc("user32", "DdeClientTransaction", HDDEDATA, (LPBYTE, DWORD, HCONV, HSZ, UINT, UINT, DWORD, LPDWORD)) 250 | Connect = get_winfunc("user32", "DdeConnect", HCONV, (DWORD, HSZ, HSZ, PCONVCONTEXT)) 251 | CreateDataHandle = get_winfunc("user32", "DdeCreateDataHandle", HDDEDATA, (DWORD, LPBYTE, DWORD, DWORD, HSZ, UINT, UINT)) 252 | CreateStringHandle = get_winfunc("user32", "DdeCreateStringHandleW", HSZ, (DWORD, LPCWSTR, UINT)) # Unicode version 253 | #CreateStringHandle = get_winfunc("user32", "DdeCreateStringHandleA", HSZ, (DWORD, LPCSTR, UINT)) # ANSI version 254 | Disconnect = get_winfunc("user32", "DdeDisconnect", BOOL, (HCONV,)) 255 | GetLastError = get_winfunc("user32", "DdeGetLastError", UINT, (DWORD,)) 256 | Initialize = get_winfunc("user32", "DdeInitializeW", UINT, (LPDWORD, DDECALLBACK, DWORD, DWORD)) # Unicode version of DDE initialize 257 | #Initialize = get_winfunc("user32", "DdeInitializeA", UINT, (LPDWORD, DDECALLBACK, DWORD, DWORD)) # ANSI version of DDE initialize 258 | FreeDataHandle = get_winfunc("user32", "DdeFreeDataHandle", BOOL, (HDDEDATA,)) 259 | FreeStringHandle = get_winfunc("user32", "DdeFreeStringHandle", BOOL, (DWORD, HSZ)) 260 | QueryString = get_winfunc("user32", "DdeQueryStringA", DWORD, (DWORD, HSZ, LPSTR, DWORD, c_int)) # ANSI version of QueryString 261 | UnaccessData = get_winfunc("user32", "DdeUnaccessData", BOOL, (HDDEDATA,)) 262 | Uninitialize = get_winfunc("user32", "DdeUninitialize", BOOL, (DWORD,)) 263 | 264 | class DDEError(RuntimeError): 265 | """Exception raise when a DDE error occures.""" 266 | def __init__(self, msg, idInst=None): 267 | if idInst is None: 268 | RuntimeError.__init__(self, msg) 269 | else: 270 | RuntimeError.__init__(self, "DDE Error: %s (err=%s)" % (msg, hex(DDE.GetLastError(idInst)))) 271 | 272 | class DDEClient(object): 273 | """The DDEClient class. 274 | 275 | Use this class to create and manage a connection to a service/topic. To get 276 | classbacks subclass DDEClient and overwrite callback.""" 277 | 278 | def __init__(self, service, topic): 279 | """Create a connection to a service/topic.""" 280 | self._idInst = DWORD(0) # application instance identifier. 281 | self._hConv = HCONV() 282 | 283 | self._callback = DDECALLBACK(self._callback) 284 | # Initialize and register application with DDEML 285 | res = DDE.Initialize(byref(self._idInst), self._callback, APPCMD_CLIENTONLY, 0) 286 | if res != DMLERR_NO_ERROR: 287 | raise DDEError("Unable to register with DDEML (err=%s)" % hex(res)) 288 | 289 | hszServName = DDE.CreateStringHandle(self._idInst, service, CP_WINUNICODE) 290 | hszTopic = DDE.CreateStringHandle(self._idInst, topic, CP_WINUNICODE) 291 | # Try to establish conversation with the Zemax server 292 | self._hConv = DDE.Connect(self._idInst, hszServName, hszTopic, PCONVCONTEXT()) 293 | DDE.FreeStringHandle(self._idInst, hszTopic) 294 | DDE.FreeStringHandle(self._idInst, hszServName) 295 | if not self._hConv: 296 | raise DDEError("Unable to establish a conversation with server", self._idInst) 297 | 298 | def __del__(self): 299 | """Cleanup any active connections and free all DDEML resources.""" 300 | if self._hConv: 301 | DDE.Disconnect(self._hConv) 302 | if self._idInst: 303 | DDE.Uninitialize(self._idInst) 304 | 305 | def advise(self, item, stop=False): 306 | """Request updates when DDE data changes.""" 307 | hszItem = DDE.CreateStringHandle(self._idInst, item, CP_WINUNICODE) 308 | hDdeData = DDE.ClientTransaction(LPBYTE(), 0, self._hConv, hszItem, CF_TEXT, XTYP_ADVSTOP if stop else XTYP_ADVSTART, TIMEOUT_ASYNC, LPDWORD()) 309 | DDE.FreeStringHandle(self._idInst, hszItem) 310 | if not hDdeData: 311 | raise DDEError("Unable to %s advise" % ("stop" if stop else "start"), self._idInst) 312 | DDE.FreeDataHandle(hDdeData) 313 | 314 | def execute(self, command): 315 | """Execute a DDE command.""" 316 | pData = c_char_p(command) 317 | cbData = DWORD(len(command) + 1) 318 | hDdeData = DDE.ClientTransaction(pData, cbData, self._hConv, HSZ(), CF_TEXT, XTYP_EXECUTE, TIMEOUT_ASYNC, LPDWORD()) 319 | if not hDdeData: 320 | raise DDEError("Unable to send command", self._idInst) 321 | DDE.FreeDataHandle(hDdeData) 322 | 323 | def request(self, item, timeout=5000): 324 | """Request data from DDE service.""" 325 | hszItem = DDE.CreateStringHandle(self._idInst, item, CP_WINUNICODE) 326 | #hDdeData = DDE.ClientTransaction(LPBYTE(), 0, self._hConv, hszItem, CF_TEXT, XTYP_REQUEST, timeout, LPDWORD()) 327 | pdwResult = DWORD(0) 328 | hDdeData = DDE.ClientTransaction(LPBYTE(), 0, self._hConv, hszItem, CF_TEXT, XTYP_REQUEST, timeout, byref(pdwResult)) 329 | DDE.FreeStringHandle(self._idInst, hszItem) 330 | if not hDdeData: 331 | raise DDEError("Unable to request item", self._idInst) 332 | 333 | if timeout != TIMEOUT_ASYNC: 334 | pdwSize = DWORD(0) 335 | pData = DDE.AccessData(hDdeData, byref(pdwSize)) 336 | if not pData: 337 | DDE.FreeDataHandle(hDdeData) 338 | raise DDEError("Unable to access data in request function", self._idInst) 339 | DDE.UnaccessData(hDdeData) 340 | else: 341 | pData = None 342 | DDE.FreeDataHandle(hDdeData) 343 | return pData 344 | 345 | def callback(self, value, item=None): 346 | """Callback function for advice.""" 347 | print("callback: %s: %s" % (item, value)) 348 | 349 | def _callback(self, wType, uFmt, hConv, hsz1, hsz2, hDdeData, dwData1, dwData2): 350 | """DdeCallback callback function for processing Dynamic Data Exchange (DDE) 351 | transactions sent by DDEML in response to DDE events 352 | 353 | Parameters 354 | ---------- 355 | wType : transaction type (UINT) 356 | uFmt : clipboard data format (UINT) 357 | hConv : handle to conversation (HCONV) 358 | hsz1 : handle to string (HSZ) 359 | hsz2 : handle to string (HSZ) 360 | hDDedata : handle to global memory object (HDDEDATA) 361 | dwData1 : transaction-specific data (DWORD) 362 | dwData2 : transaction-specific data (DWORD) 363 | 364 | Returns 365 | ------- 366 | ret : specific to the type of transaction (HDDEDATA) 367 | """ 368 | if wType == XTYP_ADVDATA: # value of the data item has changed [hsz1 = topic; hsz2 = item; hDdeData = data] 369 | dwSize = DWORD(0) 370 | pData = DDE.AccessData(hDdeData, byref(dwSize)) 371 | if pData: 372 | item = create_string_buffer('\000' * 128) 373 | DDE.QueryString(self._idInst, hsz2, item, 128, CP_WINANSI) 374 | self.callback(pData, item.value) 375 | DDE.UnaccessData(hDdeData) 376 | return DDE_FACK 377 | else: 378 | print("Error: AccessData returned NULL! (err = %s)"% (hex(DDE.GetLastError(self._idInst)))) 379 | if wType == XTYP_DISCONNECT: 380 | print("Disconnect notification received from server") 381 | 382 | return 0 383 | 384 | def WinMSGLoop(): 385 | """Run the main windows message loop.""" 386 | LPMSG = POINTER(MSG) 387 | LRESULT = c_ulong 388 | GetMessage = get_winfunc("user32", "GetMessageW", BOOL, (LPMSG, HWND, UINT, UINT)) 389 | TranslateMessage = get_winfunc("user32", "TranslateMessage", BOOL, (LPMSG,)) 390 | # restype = LRESULT 391 | DispatchMessage = get_winfunc("user32", "DispatchMessageW", LRESULT, (LPMSG,)) 392 | 393 | msg = MSG() 394 | lpmsg = byref(msg) 395 | while GetMessage(lpmsg, HWND(), 0, 0) > 0: 396 | TranslateMessage(lpmsg) 397 | DispatchMessage(lpmsg) 398 | 399 | if __name__ == "__main__": 400 | pass 401 | 402 | -------------------------------------------------------------------------------- /pyzos/zos.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: zos.py 4 | # Purpose: COM interface class for easy ZOS-API 5 | # Licence: MIT License 6 | # This file is subject to the terms and conditions of the MIT License. 7 | # For further details, please refer to LICENSE.txt 8 | #------------------------------------------------------------------------------- 9 | """Helper functions for accessing Zemax ZOS API from Python in standalone mode. 10 | """ 11 | from __future__ import division, print_function 12 | import os as _os 13 | import sys as _sys 14 | import collections as _co 15 | import win32com.client as _comclient 16 | import pythoncom as _pythoncom 17 | import tempfile as _tempfile 18 | import time as _time 19 | from pyzos.zosutils import (ZOSPropMapper as _ZOSPropMapper, 20 | replicate_methods as _replicate_methods, 21 | inheritance_dict as _inheritance_dict, 22 | wrapped_zos_object as wrapped_zos_object) 23 | import pyzos.ddeclient as _dde 24 | 25 | 26 | #%% Custom Exceptions and Exception handling 27 | class InitializationError(Exception): pass 28 | 29 | #%% Global variables 30 | Const = None # Constants (placeholder) 31 | 32 | #%% Module helper functions 33 | def _get_python_version(): 34 | return _sys.version_info[0] 35 | 36 | def _get_constants_dict(): 37 | const_dict = _comclient.constants.__dicts__[0] 38 | if _get_python_version() == 2: 39 | return const_dict 40 | else: 41 | return dict(const_dict) 42 | 43 | def _get_sync_ui_filename(): 44 | temp_dir = _tempfile.gettempdir() 45 | temp_file = 'pyzos_ui_sync_file_{}.zmx'.format(_os.getpid()) 46 | return _os.path.join(temp_dir, temp_file) 47 | 48 | def _get_new_dde_link(): 49 | ln = _PyZDDE() 50 | ln.zDDEInit() 51 | return ln 52 | 53 | def _delete_file(fileName, n=10): 54 | """Cleanly deletes a file in `n` attempts (if necessary)""" 55 | status = False 56 | count = 0 57 | while not status and count < n: 58 | try: 59 | _os.remove(fileName) 60 | except OSError: 61 | count += 1 62 | _time.sleep(0.2) 63 | else: 64 | status = True 65 | return status 66 | 67 | 68 | #%% _PyZDDE class (stripped down) 69 | class _PyZDDE(object): 70 | """Class for communicating with Zemax using DDE""" 71 | chNum = -1 72 | liveCh = 0 73 | server = 0 74 | 75 | def __init__(self): 76 | _PyZDDE.chNum += 1 77 | self.appName = "ZEMAX" + str(_PyZDDE.chNum) if _PyZDDE.chNum > 0 else "ZEMAX" 78 | self.connection = False 79 | 80 | def zDDEInit(self): 81 | """Initiates link with OpticStudio DDE server""" 82 | self.pyver = _get_python_version() 83 | # do this only one time or when there is no channel 84 | if _PyZDDE.liveCh==0: 85 | try: 86 | _PyZDDE.server = _dde.CreateServer() 87 | _PyZDDE.server.Create("ZCLIENT") 88 | except Exception as err: 89 | _sys.stderr.write("{}: DDE server may be in use!".format(str(err))) 90 | return -1 91 | # Try to create individual conversations for each ZEMAX application. 92 | self.conversation = _dde.CreateConversation(_PyZDDE.server) 93 | try: 94 | self.conversation.ConnectTo(self.appName, " ") 95 | except Exception as err: 96 | _sys.stderr.write("{}.\nOpticStudio UI may not be running!\n".format(str(err))) 97 | # should close the DDE server if it exist 98 | self.zDDEClose() 99 | return -1 100 | else: 101 | _PyZDDE.liveCh += 1 102 | self.connection = True 103 | return 0 104 | 105 | def zDDEClose(self): 106 | """Close the DDE link with Zemax server""" 107 | if _PyZDDE.server and not _PyZDDE.liveCh: 108 | _PyZDDE.server.Shutdown(self.conversation) 109 | _PyZDDE.server = 0 110 | elif _PyZDDE.server and self.connection and _PyZDDE.liveCh == 1: 111 | _PyZDDE.server.Shutdown(self.conversation) 112 | self.connection = False 113 | self.appName = '' 114 | _PyZDDE.liveCh -= 1 115 | _PyZDDE.server = 0 116 | elif self.connection: 117 | _PyZDDE.server.Shutdown(self.conversation) 118 | self.connection = False 119 | self.appName = '' 120 | _PyZDDE.liveCh -= 1 121 | return 0 122 | 123 | def setTimeout(self, time): 124 | """Set global timeout value, in seconds, for all DDE calls""" 125 | self.conversation.SetDDETimeout(round(time)) 126 | return self.conversation.GetDDETimeout() 127 | 128 | def _sendDDEcommand(self, cmd, timeout=None): 129 | """Send command to DDE client""" 130 | reply = self.conversation.Request(cmd, timeout) 131 | if self.pyver > 2: 132 | reply = reply.decode('ascii').rstrip() 133 | return reply 134 | 135 | def __del__(self): 136 | self.zDDEClose() 137 | 138 | def zGetFile(self): 139 | """Returns the full name of the lens file in DDE server""" 140 | reply = self._sendDDEcommand('GetFile') 141 | return reply.rstrip() 142 | 143 | def zGetRefresh(self): 144 | """Copy lens data from the LDE into the Zemax DDE server""" 145 | reply = None 146 | reply = self._sendDDEcommand('GetRefresh') 147 | if reply: 148 | return int(reply) #Note: Zemax returns -1 if GetRefresh fails. 149 | else: 150 | return -998 151 | 152 | def zGetUpdate(self): 153 | """Update the lens""" 154 | status,ret = -998, None 155 | ret = self._sendDDEcommand("GetUpdate") 156 | if ret != None: 157 | status = int(ret) #Note: Zemax returns -1 if GetUpdate fails. 158 | return status 159 | 160 | def zGetVersion(self): 161 | """Get the version of Zemax """ 162 | return int(self._sendDDEcommand("GetVersion")) 163 | 164 | def zLoadFile(self, fileName, append=None): 165 | """Loads a zmx file into the DDE server""" 166 | reply = None 167 | if append: 168 | cmd = "LoadFile,{},{}".format(fileName, append) 169 | else: 170 | cmd = "LoadFile,{}".format(fileName) 171 | reply = self._sendDDEcommand(cmd) 172 | if reply: 173 | return int(reply) #Note: Zemax returns -999 if update fails. 174 | else: 175 | return -998 176 | 177 | def zPushLens(self, update=None, timeout=None): 178 | """Copy lens in the Zemax DDE server into LDE""" 179 | reply = None 180 | if update == 1: 181 | reply = self._sendDDEcommand('PushLens,1', timeout) 182 | elif update == 0 or update is None: 183 | reply = self._sendDDEcommand('PushLens,0', timeout) 184 | else: 185 | raise ValueError('Invalid value for flag') 186 | if reply: 187 | return int(reply) # Note: Zemax returns -999 if push lens fails 188 | else: 189 | return -998 190 | 191 | def zPushLensPermission(self): 192 | status = None 193 | status = self._sendDDEcommand('PushLensPermission') 194 | return int(status) 195 | 196 | def zSaveFile(self, fileName): 197 | """Saves the lens currently loaded in the server to a Zemax file """ 198 | cmd = "SaveFile,{}".format(fileName) 199 | reply = self._sendDDEcommand(cmd) 200 | return int(float(reply.rstrip())) 201 | 202 | 203 | #%% ZOS API Application Class 204 | class _PyZOSApp(object): 205 | """Wrapper class for ZOS-API application.""" 206 | app = None 207 | connect = None 208 | 209 | def __new__(cls): 210 | global Const 211 | if not cls.app: 212 | # ensure win32com support files for ZOSAPI_Interfaces are available, 213 | # generate if necessary. 214 | _comclient.gencache.EnsureModule('ZOSAPI_Interfaces', 0, 1, 0) 215 | edispatch = _comclient.gencache.EnsureDispatch 216 | cls.connect = edispatch('ZOSAPI.ZOSAPI_Connection') 217 | cls.app = cls.connect.CreateNewApplication() 218 | if cls.connect.IsAlive: 219 | Const = type('Const', (), _get_constants_dict()) # Constants class 220 | else: 221 | raise InitializationError("Couldn't connect to OpticStudio; " 222 | "Ensure hw/sw/net license key is properly installed." ) 223 | return cls.app 224 | 225 | #%% Optical System Class 226 | class OpticalSystem(object): 227 | """Wrapper class for for IOpticalSystem interface. 228 | """ 229 | _instantiated = False 230 | _pyzosapp = None 231 | _dde_link = None 232 | 233 | # Patch managed properties of IOpticalSystem's base classes 234 | # Not required for now ... IOpticalSystem doesn't have any base class (currently) 235 | 236 | # Patch managed properties of ZOS IOpticalSystem 237 | pAnalyses = _ZOSPropMapper('_iopticalsystem', 'Analyses') 238 | pIsNonAxial = _ZOSPropMapper('_iopticalsystem', 'IsNonAxial') 239 | pLDE = _ZOSPropMapper('_iopticalsystem', 'LDE') 240 | pMCE = _ZOSPropMapper('_iopticalsystem', 'MCE') 241 | pMFE = _ZOSPropMapper('_iopticalsystem', 'MFE') 242 | pMode = _ZOSPropMapper('_iopticalsystem', 'Mode') 243 | pNeedsSave = _ZOSPropMapper('_iopticalsystem', 'NeedsSave') 244 | pNCE = _ZOSPropMapper('_iopticalsystem', 'NCE') 245 | pSystemData = _ZOSPropMapper('_iopticalsystem', 'SystemData') 246 | pSystemFile = _ZOSPropMapper('_iopticalsystem', 'SystemFile') 247 | pSystemID = _ZOSPropMapper('_iopticalsystem', 'SystemID') 248 | pSystemName = _ZOSPropMapper('_iopticalsystem', 'SystemName', setter=True) 249 | pTDE = _ZOSPropMapper('_iopticalsystem', 'TDE') 250 | pTheApplication = _ZOSPropMapper('_iopticalsystem', 'TheApplication') 251 | pTools = _ZOSPropMapper('_iopticalsystem', 'Tools') 252 | 253 | def __init__(self, sync_ui=False, mode=0): 254 | """Returns instance of PyZOS Optical System Interface 255 | 256 | Parameters 257 | ---------- 258 | sync_ui : boolean 259 | If `True`, then syncing mechanism with a running UI is activated. 260 | mode : integer (0 or 1) 261 | Sequential (0) or Non-sequential (1) mode 262 | 263 | Returns 264 | ------- 265 | osys : pyzos object 266 | instance of wrapped IOpticalSystem ZOS object 267 | """ 268 | self._iopticalsystem = None 269 | if OpticalSystem._instantiated: 270 | self._iopticalsystem = OpticalSystem._pyzosapp.CreateNewSystem(mode) # wrapped object 271 | else: 272 | try: 273 | OpticalSystem._pyzosapp = _PyZOSApp() # wrapped object 274 | except InitializationError as e: 275 | print('Error: {}'.format(str(e))) 276 | except _pythoncom.com_error as e: 277 | print("Error: Exception occured during ZOS initialization.") 278 | else: 279 | self._iopticalsystem = OpticalSystem._pyzosapp.GetSystemAt(0) # PrimarySystem 280 | if mode == 1: 281 | self._iopticalsystem.MakeNonSequential() 282 | OpticalSystem._instantiated = True 283 | 284 | # Store ZOS IOpticalSystem's base class(es) 285 | self._base_cls_list = _inheritance_dict.get('IOpticalSystem', None) 286 | # mark object as wrapped to prevent it from being wrapped subsequently 287 | self._wrapped = True 288 | 289 | ## activate PyZDDE if sync_ui requested 290 | self._sync_ui = False 291 | self._sync_ui_file = None 292 | self._file_to_save_on_Save = None 293 | if sync_ui: 294 | self.zSyncWithUI() 295 | 296 | ## patch methods from base class of IOpticalSystem to the wrapped object 297 | if self._base_cls_list: 298 | for base_cls_name in self._base_cls_list: 299 | _replicate_methods(_comclient.CastTo(self._iopticalsystem, base_cls_name), self) 300 | 301 | ## patch methods from ZOS IOpticalSystem to the wrapped object 302 | if self._iopticalsystem: 303 | _replicate_methods(self._iopticalsystem, self) 304 | 305 | # Provide a way to make property calls without the prefix p, 306 | def __getattr__(self, attrname): 307 | return wrapped_zos_object(getattr(self._iopticalsystem, attrname)) 308 | 309 | def __repr__(self): 310 | return "{.__name__}(sync_ui={}, mode={})".format(type(self), self._sync_ui, self.pMode) 311 | 312 | def __del__(self): 313 | if self._sync_ui_file: 314 | ext_dict = ['.zmx', '.ZMX', '.CFG', '.SES', '.ZDA'] 315 | filename_bar_ext = self._sync_ui_file.rsplit('.')[0] 316 | for ext in ext_dict: 317 | if _os.path.exists(filename_bar_ext + ext): 318 | _delete_file(filename_bar_ext + ext) 319 | if OpticalSystem._dde_link: 320 | OpticalSystem._dde_link.zDDEClose() ##TODO: FIX should probably have a reference count??? 321 | 322 | #%% UI sync machinery 323 | def zSyncWithUI(self): 324 | """Turn on sync-with-ui""" 325 | if not OpticalSystem._dde_link: 326 | OpticalSystem._dde_link = _get_new_dde_link() 327 | if not self._sync_ui_file: 328 | self._sync_ui_file = _get_sync_ui_filename() 329 | self._sync_ui = True 330 | 331 | def zPushLens(self, update=None): 332 | """Push lens in ZOS COM server to UI""" 333 | self.SaveAs(self._sync_ui_file) 334 | OpticalSystem._dde_link.zLoadFile(self._sync_ui_file) 335 | OpticalSystem._dde_link.zPushLens(update) 336 | 337 | def zGetRefresh(self): 338 | """Copy lens in UI to headless ZOS COM server""" 339 | OpticalSystem._dde_link.zGetRefresh() 340 | OpticalSystem._dde_link.zSaveFile(self._sync_ui_file) 341 | self._iopticalsystem.LoadFile (self._sync_ui_file, False) 342 | 343 | #%% Overridden Methods 344 | def SaveAs(self, filename): 345 | """Saves the current system to the specified file. 346 | 347 | @param filename: absolute path (string) 348 | @return: None 349 | @raise: ValueError if path (excluding the zemax file name) is not valid 350 | 351 | All future calls to `Save()` will use the same file. 352 | """ 353 | directory, zfile = _os.path.split(filename) 354 | if zfile.startswith('pyzos_ui_sync_file'): 355 | self._iopticalsystem.SaveAs(filename) 356 | else: # regular file 357 | if not _os.path.exists(directory): 358 | raise ValueError('{} is not valid.'.format(directory)) 359 | else: 360 | self._file_to_save_on_Save = filename # store to use in Save() 361 | self._iopticalsystem.SaveAs(filename) 362 | 363 | def Save(self): 364 | """Saves the current system""" 365 | # This method is intercepted to allow ui_sync 366 | if self._file_to_save_on_Save: 367 | self._iopticalsystem.SaveAs(self._file_to_save_on_Save) 368 | else: 369 | self._iopticalsystem.Save() 370 | 371 | #%% Extra / Custom Properties 372 | @property 373 | def pConnectIsAlive(self): 374 | """ZOS-API connection active/inactive status""" 375 | return _PyZOSApp.connect.IsAlive 376 | 377 | #%% Extra / Custom methods 378 | def zGetSurfaceData(self, surfNum): 379 | """Return surface data""" 380 | if self.pMode == 0: # Sequential mode 381 | surf_data = _co.namedtuple('surface_data', ['radius', 'thick', 'material', 'semidia', 382 | 'conic', 'comment']) 383 | surf = self.pLDE.GetSurfaceAt(surfNum) 384 | return surf_data(surf.pRadius, surf.pThickness, surf.pMaterial, surf.pSemiDiameter, 385 | surf.pConic, surf.pComment) 386 | else: 387 | raise NotImplementedError('Function not implemented for non-sequential mode') 388 | 389 | def zInsertNewSurfaceAt(self, surfNum): 390 | if self.pMode == 0: 391 | lde = self.pLDE 392 | lde.InsertNewSurfaceAt(surfNum) 393 | else: 394 | raise NotImplementedError('Function not implemented for non-sequential mode') 395 | 396 | def zSetSurfaceData(self, surfNum, radius=None, thick=None, material=None, semidia=None, 397 | conic=None, comment=None): 398 | """Sets surface data""" 399 | if self.pMode == 0: # Sequential mode 400 | surf = self.pLDE.GetSurfaceAt(surfNum) 401 | if radius is not None: 402 | surf.pRadius = radius 403 | if thick is not None: 404 | surf.pThickness = thick 405 | if material is not None: 406 | surf.pMaterial = material 407 | if semidia is not None: 408 | surf.pSemiDiameter = semidia 409 | if conic is not None: 410 | surf.pConic = conic 411 | if comment is not None: 412 | surf.pComment = comment 413 | else: 414 | raise NotImplementedError('Function not implemented for non-sequential mode') 415 | 416 | def zSetDefaultMeritFunctionSEQ(self, ofType=0, ofData=0, ofRef=0, pupilInteg=0, rings=0, 417 | arms=0, obscuration=0, grid=0, delVignetted=False, useGlass=False, 418 | glassMin=0, glassMax=1000, glassEdge=0, useAir=False, airMin=0, 419 | airMax=1000, airEdge=0, axialSymm=True, ignoreLatCol=False, 420 | addFavOper=False, startAt=1, relativeXWgt=1.0, overallWgt=1.0, 421 | configNum=0): 422 | """Sets the default merit function for Sequential Merit Function Editor 423 | 424 | Parameters 425 | ---------- 426 | ofType : integer 427 | optimization function type (0=RMS, ...) 428 | ofData : integer 429 | optimization function data (0=Wavefront, 1=Spot Radius, ...) 430 | ofRef : integer 431 | optimization function reference (0=Centroid, ...) 432 | pupilInteg : integer 433 | pupil integration method (0=Gaussian Quadrature, 1=Rectangular Array) 434 | rings : integer 435 | rings (0=1, 1=2, 2=3, 3=4, ...) 436 | arms : integer 437 | arms (0=6, 1=8, 2=10, 3=12) 438 | obscuration : real 439 | obscuration 440 | delVignetted : boolean 441 | delete vignetted ? 442 | useGlass : boolean 443 | whether to use Glass settings for thickness boundary 444 | glassMin : real 445 | glass mininum thickness 446 | glassMax : real 447 | glass maximum thickness 448 | glassEdge : real 449 | glass edge thickness 450 | useAir : boolean 451 | whether to use Air settings for thickness boundary 452 | airMin : real 453 | air minimum thickness 454 | airMax : real 455 | air maximum thickness 456 | airEdge : real 457 | air edge thickness 458 | axialSymm : boolean 459 | assume axial symmetry 460 | ignoreLatCol : boolean 461 | ignore latent color 462 | addFavOper : boolean 463 | add favorite color 464 | configNum : integer 465 | configuration number (0=All) 466 | startAt : integer 467 | start at 468 | relativeXWgt : real 469 | relative X weight 470 | overallWgt : real 471 | overall weight 472 | """ 473 | mfe = self.pMFE 474 | wizard = mfe.pSEQOptimizationWizard 475 | wizard.pType = ofType 476 | wizard.pData = ofData 477 | wizard.pReference = ofRef 478 | wizard.pPupilIntegrationMethod = pupilInteg 479 | wizard.pRing = rings 480 | wizard.pArm = arms 481 | wizard.pObscuration = obscuration 482 | wizard.pGrid = grid 483 | wizard.pIsDeleteVignetteUsed = delVignetted 484 | wizard.pIsGlassUsed = useGlass 485 | wizard.pGlassMin = glassMin 486 | wizard.pGlassMax = glassMax 487 | wizard.pGlassEdge = glassEdge 488 | wizard.pIsAirUsed = useAir 489 | wizard.pAirMin = airMin 490 | wizard.pAirMax = airMax 491 | wizard.pAirEdge = airEdge 492 | wizard.pIsAssumeAxialSymmetryUsed = axialSymm 493 | wizard.pIsIgnoreLateralColorUsed = ignoreLatCol 494 | wizard.pConfiguration = configNum 495 | wizard.pIsAddFavoriteOperandsUsed = addFavOper 496 | wizard.pStartAt = startAt 497 | wizard.pRelativeXWeight = relativeXWgt 498 | wizard.pOverallWeight = overallWgt 499 | wizard.CommonSettings.OK() # Settings are set, perform the wizardry. 500 | -------------------------------------------------------------------------------- /pyzos/zos_obj_override/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzos/pyzos/da6bf3296b0154ccee44ad9a4286055ae031ecc7/pyzos/zos_obj_override/__init__.py -------------------------------------------------------------------------------- /pyzos/zos_obj_override/i_analyses_methods.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: i_analyses_methods.py 4 | # Purpose: store custom methods for wrapper class of I_Analyses 5 | # Licence: MIT License 6 | #------------------------------------------------------------------------------- 7 | """Store custom methods for wrapper class of I_Analyses. 8 | name := repr(zos_obj).split()[0].split('.')[-1].lower() + '_methods.py' 9 | """ 10 | from __future__ import print_function 11 | from __future__ import division 12 | from win32com.client import CastTo as _CastTo, constants as _constants 13 | from pyzos.zosutils import wrapped_zos_object as _wrapped_zos_object 14 | 15 | # overridden methods 16 | # ------------------ 17 | 18 | 19 | # Custom methods 20 | # -------------- -------------------------------------------------------------------------------- /pyzos/zos_obj_override/ia__methods.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: ia__methods.py 4 | # Purpose: store custom methods for wrapper class of IA_ Interface, the Base 5 | # interface for all analysis windows. 6 | # Licence: MIT License 7 | #------------------------------------------------------------------------------- 8 | """Store custom methods for wrapper class of IA_ Interface. 9 | name := repr(zos_obj).split()[0].split('.')[-1].lower() + '_methods.py' 10 | """ 11 | from __future__ import print_function 12 | from __future__ import division 13 | import warnings as _warnings 14 | from win32com.client import CastTo as _CastTo, constants as _constants 15 | from pyzos.zosutils import (wrapped_zos_object as _wrapped_zos_object, 16 | replicate_methods as _replicate_methods) 17 | 18 | # Overridden methods 19 | # ------------------ 20 | 21 | def GetSettings(self): 22 | # the IA_.GetSettings() returns IAS_ which is the base class of all other settings 23 | # objects such as IAS_FftMtf, IAS_FftMap, etc. The base-class IAS_ objects needs to be 24 | # "specialized" for a particular analysis using the _CastTo function. When we apply 25 | # CastTo(), the specialized objects "gain" the specific analysis functions and properties. 26 | # before returning, when we invoke _wrapped_zos_object(), the base-class methods and 27 | # properties also gets patched. 28 | 29 | settings_base = self._ia_.GetSettings() 30 | 31 | # CastTo the specialized class to add properties if they corresponding 32 | # interface class is available in the library, else return generic IAS_ 33 | try: 34 | if self._ia_.AnalysisType == _constants.AnalysisIDM_RayFan: 35 | settings = _CastTo(settings_base, 'IAS_RayFan') 36 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_RayTrace: 37 | settings = _CastTo(settings_base, 'IAS_RayTrace') 38 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_OpticalPathFan: 39 | settings = _CastTo(settings_base, 'IAS_OpticalPathFan') 40 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PupilAberrationFan: 41 | settings = _CastTo(settings_base, 'IAS_PupilAberrationFan') 42 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FieldCurvatureAndDistortion: 43 | settings = _CastTo(settings_base, 'IAS_FieldCurvatureAndDistortion') 44 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FocalShiftDiagram: 45 | settings = _CastTo(settings_base, 'IAS_FocalShiftDiagram') 46 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_GridDistortion: 47 | settings = _CastTo(settings_base, 'IAS_GridDistortion') 48 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_LateralColor: 49 | settings = _CastTo(settings_base, 'IAS_LateralColor') 50 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_LongitudinalAberration: 51 | settings = _CastTo(settings_base, 'IAS_LongitudinalAberration') 52 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SeidelCoefficients: 53 | settings = _CastTo(settings_base, 'IAS_SeidelCoefficients') 54 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SeidelDiagram: 55 | settings = _CastTo(settings_base, 'IAS_SeidelDiagram') 56 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ZernikeAnnularCoefficients: 57 | settings = _CastTo(settings_base, 'IAS_ZernikeAnnularCoefficients') 58 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ZernikeCoefficientsVsField: 59 | settings = _CastTo(settings_base, 'IAS_ZernikeCoefficientsVsField') 60 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ZernikeFringeCoefficients: 61 | settings = _CastTo(settings_base, 'IAS_ZernikeFringeCoefficients') 62 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ZernikeStandardCoefficients: 63 | settings = _CastTo(settings_base, 'IAS_ZernikeStandardCoefficients') 64 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FftMtf: 65 | settings = _CastTo(settings_base, 'IAS_FftMtf') 66 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FftMtfMap: 67 | settings = _CastTo(settings_base, 'IAS_FftMtfMap') 68 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FftMtfvsField: 69 | settings = _CastTo(settings_base, 'IAS_FftMtfvsField') 70 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FftSurfaceMtf: 71 | settings = _CastTo(settings_base, 'IAS_FftSurfaceMtf') 72 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FftThroughFocusMtf: 73 | settings = _CastTo(settings_base, 'IAS_FftThroughFocusMtf') 74 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_GeometricMtf: 75 | settings = _CastTo(settings_base, 'IAS_GeometricMtf') 76 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_GeometricMtfMap: 77 | settings = _CastTo(settings_base, 'IAS_GeometricMtfMap') 78 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_GeometricMtfvsField: 79 | settings = _CastTo(settings_base, 'IAS_GeometricMtfvsField') 80 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_GeometricThroughFocusMtf: 81 | settings = _CastTo(settings_base, 'IAS_GeometricThroughFocusMtf') 82 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_HuygensMtf: 83 | settings = _CastTo(settings_base, 'IAS_HuygensMtf') 84 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_HuygensMtfvsField: 85 | settings = _CastTo(settings_base, 'IAS_HuygensMtfvsField') 86 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_HuygensSurfaceMtf: 87 | settings = _CastTo(settings_base, 'IAS_HuygensSurfaceMtf') 88 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_HuygensThroughFocusMtf: 89 | settings = _CastTo(settings_base, 'IAS_HuygensThroughFocusMtf') 90 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FftPsf: 91 | settings = _CastTo(settings_base, 'IAS_FftPsf') 92 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FftPsfCrossSection: 93 | settings = _CastTo(settings_base, 'IAS_FftPsfCrossSection') 94 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FftPsfLineEdgeSpread: 95 | settings = _CastTo(settings_base, 'IAS_FftPsfLineEdgeSpread') 96 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_HuygensPsfCrossSection: 97 | settings = _CastTo(settings_base, 'IAS_HuygensPsfCrossSection') 98 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_HuygensPsf: 99 | settings = _CastTo(settings_base, 'IAS_HuygensPsf') 100 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_DiffractionEncircledEnergy: 101 | settings = _CastTo(settings_base, 'IAS_DiffractionEncircledEnergy') 102 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_GeometricEncircledEnergy: 103 | settings = _CastTo(settings_base, 'IAS_GeometricEncircledEnergy') 104 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_GeometricLineEdgeSpread: 105 | settings = _CastTo(settings_base, 'IAS_GeometricLineEdgeSpread') 106 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ExtendedSourceEncircledEnergy: 107 | settings = _CastTo(settings_base, 'IAS_ExtendedSourceEncircledEnergy') 108 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SurfaceCurvatureCross: 109 | settings = _CastTo(settings_base, 'IAS_SurfaceCurvatureCross') 110 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SurfacePhaseCross: 111 | settings = _CastTo(settings_base, 'IAS_SurfacePhaseCross') 112 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SurfaceSagCross: 113 | settings = _CastTo(settings_base, 'IAS_SurfaceSagCross') 114 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SurfaceCurvature: 115 | settings = _CastTo(settings_base, 'IAS_SurfaceCurvature') 116 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SurfacePhase: 117 | settings = _CastTo(settings_base, 'IAS_SurfacePhase') 118 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SurfaceSag: 119 | settings = _CastTo(settings_base, 'IAS_SurfaceSag') 120 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_StandardSpot: 121 | settings = _CastTo(settings_base, 'IAS_StandardSpot') 122 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ThroughFocusSpot: 123 | settings = _CastTo(settings_base, 'IAS_ThroughFocusSpot') 124 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FullFieldSpot: 125 | settings = _CastTo(settings_base, 'IAS_FullFieldSpot') 126 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_MatrixSpot: 127 | settings = _CastTo(settings_base, 'IAS_MatrixSpot') 128 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ConfigurationMatrixSpot: 129 | settings = _CastTo(settings_base, 'IAS_ConfigurationMatrixSpot') 130 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_RMSField: 131 | settings = _CastTo(settings_base, 'IAS_RMSField') 132 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_RMSFieldMap: 133 | settings = _CastTo(settings_base, 'IAS_RMSFieldMap') 134 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_RMSLambdaDiagram: 135 | settings = _CastTo(settings_base, 'IAS_RMSLambdaDiagram') 136 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_RMSFocus: 137 | settings = _CastTo(settings_base, 'IAS_RMSFocus') 138 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_Foucault: 139 | settings = _CastTo(settings_base, 'IAS_Foucault') 140 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_Interferogram: 141 | settings = _CastTo(settings_base, 'IAS_Interferogram') 142 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_WavefrontMap: 143 | settings = _CastTo(settings_base, 'IAS_WavefrontMap') 144 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_DetectorViewer: 145 | settings = _CastTo(settings_base, 'IAS_DetectorViewer') 146 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_Draw2D: 147 | settings = _CastTo(settings_base, 'IAS_Draw2D') 148 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_Draw3D: 149 | settings = _CastTo(settings_base, 'IAS_Draw3D') 150 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ImageSimulation: 151 | settings = _CastTo(settings_base, 'IAS_ImageSimulation') 152 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_GeometricImageAnalysis: 153 | settings = _CastTo(settings_base, 'IAS_GeometricImageAnalysis') 154 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_IMABIMFileViewer: 155 | settings = _CastTo(settings_base, 'IAS_IMABIMFileViewer') 156 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_GeometricBitmapImageAnalysis: 157 | settings = _CastTo(settings_base, 'IAS_GeometricBitmapImageAnalysis') 158 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_BitmapFileViewer: 159 | settings = _CastTo(settings_base, 'IAS_BitmapFileViewer') 160 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_LightSourceAnalysis: 161 | settings = _CastTo(settings_base, 'IAS_LightSourceAnalysis') 162 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PartiallyCoherentImageAnalysis: 163 | settings = _CastTo(settings_base, 'IAS_PartiallyCoherentImageAnalysis') 164 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ExtendedDiffractionImageAnalysis: 165 | settings = _CastTo(settings_base, 'IAS_ExtendedDiffractionImageAnalysis') 166 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_BiocularFieldOfViewAnalysis: 167 | settings = _CastTo(settings_base, 'IAS_BiocularFieldOfViewAnalysis') 168 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_BiocularDipvergenceConvergence: 169 | settings = _CastTo(settings_base, 'IAS_BiocularDipvergenceConvergence') 170 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_RelativeIllumination: 171 | settings = _CastTo(settings_base, 'IAS_RelativeIllumination') 172 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_VignettingDiagramSettings: 173 | settings = _CastTo(settings_base, 'IAS_VignettingDiagramSettings') 174 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FootprintSettings: 175 | settings = _CastTo(settings_base, 'IAS_FootprintSettings') 176 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_YYbarDiagram: 177 | settings = _CastTo(settings_base, 'IAS_YYbarDiagram') 178 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PowerFieldMapSettings: 179 | settings = _CastTo(settings_base, 'IAS_PowerFieldMapSettings') 180 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PowerPupilMapSettings: 181 | settings = _CastTo(settings_base, 'IAS_PowerPupilMapSettings') 182 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_IncidentAnglevsImageHeight: 183 | settings = _CastTo(settings_base, 'IAS_IncidentAnglevsImageHeight') 184 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FiberCouplingSettings: 185 | settings = _CastTo(settings_base, 'IAS_FiberCouplingSettings') 186 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_YNIContributions: 187 | settings = _CastTo(settings_base, 'IAS_YNIContributions') 188 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SagTable: 189 | settings = _CastTo(settings_base, 'IAS_SagTable') 190 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_CardinalPoints: 191 | settings = _CastTo(settings_base, 'IAS_CardinalPoints') 192 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_DispersionDiagram: 193 | settings = _CastTo(settings_base, 'IAS_DispersionDiagram') 194 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_GlassMap: 195 | settings = _CastTo(settings_base, 'IAS_GlassMap') 196 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_AthermalGlassMap: 197 | settings = _CastTo(settings_base, 'IAS_AthermalGlassMap') 198 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_InternalTransmissionvsWavelength: 199 | settings = _CastTo(settings_base, 'IAS_InternalTransmissionvsWavelength') 200 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_DispersionvsWavelength: 201 | settings = _CastTo(settings_base, 'IAS_DispersionvsWavelength') 202 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_GrinProfile: 203 | settings = _CastTo(settings_base, 'IAS_GrinProfile') 204 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_GradiumProfile: 205 | settings = _CastTo(settings_base, 'IAS_GradiumProfile') 206 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_UniversalPlot1D: 207 | settings = _CastTo(settings_base, 'IAS_UniversalPlot1D') 208 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_UniversalPlot2D: 209 | settings = _CastTo(settings_base, 'IAS_UniversalPlot2D') 210 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PolarizationRayTrace: 211 | settings = _CastTo(settings_base, 'IAS_PolarizationRayTrace') 212 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PolarizationPupilMap: 213 | settings = _CastTo(settings_base, 'IAS_PolarizationPupilMap') 214 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_Transmission: 215 | settings = _CastTo(settings_base, 'IAS_Transmission') 216 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PhaseAberration: 217 | settings = _CastTo(settings_base, 'IAS_PhaseAberration') 218 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_TransmissionFan: 219 | settings = _CastTo(settings_base, 'IAS_TransmissionFan') 220 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ParaxialGaussianBeam: 221 | settings = _CastTo(settings_base, 'IAS_ParaxialGaussianBeam') 222 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SkewGaussianBeam: 223 | settings = _CastTo(settings_base, 'IAS_SkewGaussianBeam') 224 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PhysicalOpticsPropagation: 225 | settings = _CastTo(settings_base, 'IAS_PhysicalOpticsPropagation') 226 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_BeamFileViewer: 227 | settings = _CastTo(settings_base, 'IAS_BeamFileViewer') 228 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ReflectionvsAngle: 229 | settings = _CastTo(settings_base, 'IAS_ReflectionvsAngle') 230 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_TransmissionvsAngle: 231 | settings = _CastTo(settings_base, 'IAS_TransmissionvsAngle') 232 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_AbsorptionvsAngle: 233 | settings = _CastTo(settings_base, 'IAS_AbsorptionvsAngle') 234 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_DiattenuationvsAngle: 235 | settings = _CastTo(settings_base, 'IAS_DiattenuationvsAngle') 236 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PhasevsAngle: 237 | settings = _CastTo(settings_base, 'IAS_PhasevsAngle') 238 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_RetardancevsAngle: 239 | settings = _CastTo(settings_base, 'IAS_RetardancevsAngle') 240 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ReflectionvsWavelength: 241 | settings = _CastTo(settings_base, 'IAS_ReflectionvsWavelength') 242 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_TransmissionvsWavelength: 243 | settings = _CastTo(settings_base, 'IAS_TransmissionvsWavelength') 244 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_AbsorptionvsWavelength: 245 | settings = _CastTo(settings_base, 'IAS_AbsorptionvsWavelength') 246 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_DiattenuationvsWavelength: 247 | settings = _CastTo(settings_base, 'IAS_DiattenuationvsWavelength') 248 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PhasevsWavelength: 249 | settings = _CastTo(settings_base, 'IAS_PhasevsWavelength') 250 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_RetardancevsWavelength: 251 | settings = _CastTo(settings_base, 'IAS_RetardancevsWavelength') 252 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_DirectivityPlot: 253 | settings = _CastTo(settings_base, 'IAS_DirectivityPlot') 254 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SourcePolarViewer: 255 | settings = _CastTo(settings_base, 'IAS_SourcePolarViewer') 256 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PhotoluminscenceViewer: 257 | settings = _CastTo(settings_base, 'IAS_PhotoluminscenceViewer') 258 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SourceSpectrumViewer: 259 | settings = _CastTo(settings_base, 'IAS_SourceSpectrumViewer') 260 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_RadiantSourceModelViewerSettings: 261 | settings = _CastTo(settings_base, 'IAS_RadiantSourceModelViewerSettings') 262 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SurfaceDataSettings: 263 | settings = _CastTo(settings_base, 'IAS_SurfaceDataSettings') 264 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PrescriptionDataSettings: 265 | settings = _CastTo(settings_base, 'IAS_PrescriptionDataSettings') 266 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FileComparatorSettings: 267 | settings = _CastTo(settings_base, 'IAS_FileComparatorSettings') 268 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PartViewer: 269 | settings = _CastTo(settings_base, 'IAS_PartViewer') 270 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ReverseRadianceAnalysis: 271 | settings = _CastTo(settings_base, 'IAS_ReverseRadianceAnalysis') 272 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PathAnalysis: 273 | settings = _CastTo(settings_base, 'IAS_PathAnalysis') 274 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_FluxvsWavelength: 275 | settings = _CastTo(settings_base, 'IAS_FluxvsWavelength') 276 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_RoadwayLighting: 277 | settings = _CastTo(settings_base, 'IAS_RoadwayLighting') 278 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SourceIlluminationMap: 279 | settings = _CastTo(settings_base, 'IAS_SourceIlluminationMap') 280 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ScatterFunctionViewer: 281 | settings = _CastTo(settings_base, 'IAS_ScatterFunctionViewer') 282 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ScatterPolarPlotSettings: 283 | settings = _CastTo(settings_base, 'IAS_ScatterPolarPlotSettings') 284 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ZemaxElementDrawing: 285 | settings = _CastTo(settings_base, 'IAS_ZemaxElementDrawing') 286 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ShadedModel: 287 | settings = _CastTo(settings_base, 'IAS_ShadedModel') 288 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_NSCShadedModel: 289 | settings = _CastTo(settings_base, 'IAS_NSCShadedModel') 290 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_NSC3DLayout: 291 | settings = _CastTo(settings_base, 'IAS_NSC3DLayout') 292 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_NSCObjectViewer: 293 | settings = _CastTo(settings_base, 'IAS_NSCObjectViewer') 294 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_RayDatabaseViewer: 295 | settings = _CastTo(settings_base, 'IAS_RayDatabaseViewer') 296 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_ISOElementDrawing: 297 | settings = _CastTo(settings_base, 'IAS_ISOElementDrawing') 298 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SystemData: 299 | settings = _CastTo(settings_base, 'IAS_SystemData') 300 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_TestPlateList: 301 | settings = _CastTo(settings_base, 'IAS_TestPlateList') 302 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SourceColorChart1931: 303 | settings = _CastTo(settings_base, 'IAS_SourceColorChart1931') 304 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_SourceColorChart1976: 305 | settings = _CastTo(settings_base, 'IAS_SourceColorChart1976') 306 | elif self._ia_.AnalysisType == _constants.AnalysisIDM_PrescriptionGraphic: 307 | settings = _CastTo(settings_base, 'IAS_PrescriptionGraphic') 308 | except ValueError as err: 309 | _warnings.warn("Couldn't find and cast to specialized analysis settings.", stacklevel=2) 310 | settings = settings_base 311 | 312 | # create the settings object 313 | settings = _wrapped_zos_object(settings) 314 | 315 | return settings 316 | 317 | # Extra methods 318 | # ------------- -------------------------------------------------------------------------------- /pyzos/zos_obj_override/iar__methods.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: iar__methods.py 4 | # Purpose: store custom methods for wrapper class of IAR_ Interface 5 | # Licence: MIT License 6 | #------------------------------------------------------------------------------- 7 | """Store custom methods for wrapper class of IAR_ Interface. 8 | name := repr(zos_obj).split()[0].split('.')[-1].lower() + '_methods.py' 9 | """ 10 | from __future__ import print_function 11 | from __future__ import division 12 | from win32com.client import CastTo as _CastTo, constants as _constants 13 | from pyzos.zosutils import wrapped_zos_object as _wrapped_zos_object 14 | 15 | # Overridden methods 16 | # ------------------ 17 | 18 | 19 | # Extra methods 20 | # ------------- -------------------------------------------------------------------------------- /pyzos/zos_obj_override/ifields_methods.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: ifields_methods.py 4 | # Purpose: store custom methods for wrapper class of IFields Interface 5 | # Licence: MIT License 6 | #------------------------------------------------------------------------------- 7 | """Store custom methods for wrapper class of IFields Interface. 8 | name := repr(zos_obj).split()[0].split('.')[-1].lower() + '_methods.py' 9 | """ 10 | from __future__ import print_function 11 | from __future__ import division 12 | from win32com.client import CastTo as _CastTo, constants as _constants 13 | from pyzos.zosutils import wrapped_zos_object as _wrapped_zos_object 14 | 15 | # Overridden methods 16 | # ------------------ 17 | 18 | # Extra methods 19 | # ------------- -------------------------------------------------------------------------------- /pyzos/zos_obj_override/ilderow_methods.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: ilderow_methods.py 4 | # Purpose: store custom methods for wrapper class of ILDERow Interface 5 | # Licence: MIT License 6 | #------------------------------------------------------------------------------- 7 | """Store custom methods for wrapper class of ILDERow Interface, which contains 8 | all data for a LDE surface. This interface can be accessed via the 9 | ILensDataEditor interface. 10 | name := repr(zos_obj).split()[0].split('.')[-1].lower() + '_methods.py' 11 | """ 12 | from __future__ import print_function 13 | from __future__ import division 14 | from win32com.client import CastTo as _CastTo, constants as _constants 15 | from pyzos.zosutils import wrapped_zos_object as _wrapped_zos_object 16 | -------------------------------------------------------------------------------- /pyzos/zos_obj_override/ilensdataeditor_methods.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: ilensdataeditor_methods.py 4 | # Purpose: store custom methods for wrapper class of ILensDataEditor 5 | # Licence: MIT License 6 | #------------------------------------------------------------------------------- 7 | """Store custom methods for wrapper class of ILensDataEditor, which defines 8 | all properties and methods needed to interact with the Lens Data Editor. 9 | This interface can be accessed via the IOpticalSystem interface. 10 | name := repr(zos_obj).split()[0].split('.')[-1].lower() + '_methods.py' 11 | """ 12 | from __future__ import print_function 13 | from __future__ import division 14 | import collections as _co 15 | from win32com.client import CastTo as _CastTo, constants as _constants 16 | from pyzos.zosutils import wrapped_zos_object as _wrapped_zos_object 17 | 18 | # Overridden methods 19 | # ------------------ 20 | 21 | def GetPupil(self): 22 | """Retrieve pupil data 23 | """ 24 | pupil_data = _co.namedtuple('pupil_data', ['ZemaxApertureType', 25 | 'ApertureValue', 26 | 'entrancePupilDiameter', 27 | 'entrancePupilPosition', 28 | 'exitPupilDiameter', 29 | 'exitPupilPosition', 30 | 'ApodizationType', 31 | 'ApodizationFactor']) 32 | data = self._ilensdataeditor.GetPupil() 33 | return pupil_data(*data) 34 | 35 | 36 | # Overridden properties 37 | # --------------------- 38 | 39 | 40 | # Extra methods 41 | # ------------- 42 | -------------------------------------------------------------------------------- /pyzos/zos_obj_override/ilocaloptimization_methods.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: ilocaloptimization_methods.py 4 | # Purpose: store custom methods for wrapper class of ILocalOptimization Interface 5 | # Licence: MIT License 6 | #------------------------------------------------------------------------------- 7 | """Store custom methods for wrapper class of ILocalOptimization Interface, which 8 | contains methods to run various system-wide tools. 9 | name := repr(zos_obj).split()[0].split('.')[-1].lower() + '_methods.py' 10 | """ 11 | from __future__ import print_function 12 | from __future__ import division 13 | from win32com.client import CastTo as _CastTo, constants as _constants 14 | from pyzos.zosutils import wrapped_zos_object as _wrapped_zos_object 15 | 16 | 17 | # Overridden methods 18 | # ------------------ 19 | -------------------------------------------------------------------------------- /pyzos/zos_obj_override/imeritfunctioneditor_methods.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: imeritfunctioneditor_methods.py 4 | # Purpose: store custom methods for wrapper class of IFields Interface 5 | # Licence: MIT License 6 | #------------------------------------------------------------------------------- 7 | """Store custom methods for wrapper class of IMeritFunctionEditor Interface, 8 | which interface defines all properties and methods needed to interact with 9 | the MRit Function Editor. 10 | name := repr(zos_obj).split()[0].split('.')[-1].lower() + '_methods.py' 11 | """ 12 | from __future__ import print_function 13 | from __future__ import division 14 | from win32com.client import CastTo as _CastTo, constants as _constants 15 | from pyzos.zosutils import wrapped_zos_object as _wrapped_zos_object 16 | 17 | 18 | # Overridden methods 19 | # ------------------ 20 | 21 | 22 | # Overridden properties 23 | # --------------------- 24 | 25 | 26 | -------------------------------------------------------------------------------- /pyzos/zos_obj_override/iopticalsystemtools_methods.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: iopticalsystemtools_methods.py 4 | # Purpose: store custom methods for wrapper class of IOpticalSystemTools 5 | # Interface 6 | # Licence: MIT License 7 | #------------------------------------------------------------------------------- 8 | """Store custom methods for wrapper class of IOpticalSystemTools Interface, which 9 | contains methods to run various system-wide tools. 10 | name := repr(zos_obj).split()[0].split('.')[-1].lower() + '_methods.py' 11 | """ 12 | from __future__ import print_function 13 | from __future__ import division 14 | from win32com.client import CastTo as _CastTo, constants as _constants 15 | from pyzos.zosutils import wrapped_zos_object as _wrapped_zos_object 16 | 17 | 18 | # Overridden methods 19 | # ------------------ 20 | def OpenLocalOptimization(self): 21 | local_opt = self._iopticalsystemtools.OpenLocalOptimization() 22 | if local_opt: # local_opt is None if the Local Optimization Tool is already open 23 | return _wrapped_zos_object(local_opt) 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /pyzos/zos_obj_override/isystemdata_methods.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: isystemdata_methods.py 4 | # Purpose: store custom methods for wrapper class of ISystemData Interface 5 | # Licence: MIT License 6 | #------------------------------------------------------------------------------- 7 | """Store custom methods for wrapper class of ISystemData Interface, which provides 8 | Interfaces and methods for changing all System Explorer data. This interface 9 | can be accessed via the IOpticalSystem interface. 10 | name := repr(zos_obj).split()[0].split('.')[-1].lower() + '_methods.py' 11 | """ 12 | from __future__ import print_function 13 | from __future__ import division 14 | from win32com.client import CastTo as _CastTo, constants as _constants 15 | from pyzos.zosutils import wrapped_zos_object as _wrapped_zos_object 16 | 17 | 18 | # Overridden properties 19 | # --------------------- 20 | 21 | # Extra methods 22 | # ------------- 23 | 24 | 25 | # Extra properties 26 | # ---------------- 27 | -------------------------------------------------------------------------------- /pyzos/zos_obj_override/izosapi_application_methods.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: izosapi_application_methods.py 4 | # Purpose: store custom methods for wrapper class of IZOSAPI_Application 5 | # Licence: MIT License 6 | #------------------------------------------------------------------------------- 7 | """Store custom methods for wrapper class of IZOSAPI_Application. 8 | name := repr(zos_obj).split()[0].split('.')[-1].lower() + '_methods.py' 9 | """ 10 | from __future__ import print_function 11 | from __future__ import division 12 | from win32com.client import CastTo as _CastTo, constants as _constants 13 | from pyzos.zosutils import wrapped_zos_object as _wrapped_zos_object 14 | 15 | # Overridden methods 16 | # ------------------ 17 | 18 | 19 | # Overridden properties 20 | # --------------------- 21 | 22 | 23 | 24 | # Extra methods 25 | # ------------- 26 | 27 | 28 | 29 | # Extra Properties 30 | # ---------------- 31 | 32 | -------------------------------------------------------------------------------- /pyzos/zosutils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: zosutils.py 4 | # Purpose: Utilities for pyzos 5 | # Licence: MIT License 6 | # This file is subject to the terms and conditions of the MIT License. 7 | # For further details, please refer to LICENSE.txt 8 | #------------------------------------------------------------------------------- 9 | from __future__ import division, print_function 10 | import sys as _sys 11 | from win32com.client import CastTo as _CastTo 12 | 13 | 14 | def get_callable_method_dict(obj): 15 | """Returns a dictionary of callable methods of object `obj`. 16 | 17 | @param obj: ZOS API Python COM object 18 | @return: a dictionary of callable methods 19 | 20 | Notes: 21 | the function only returns the callable attributes that are listed by dir() 22 | function. Properties are not returned. 23 | """ 24 | methodDict = {} 25 | for methodStr in dir(obj): 26 | method = getattr(obj, methodStr, 'none') 27 | if callable(method) and not methodStr.startswith('_'): 28 | methodDict[methodStr] = method 29 | return methodDict 30 | 31 | def replicate_methods(srcObj, dstObj): 32 | """Replicate callable methods from a `srcObj` to `dstObj` (generally a wrapper object). 33 | 34 | @param srcObj: source object 35 | @param dstObj: destination object of the same type. 36 | @return : none 37 | 38 | Implementer notes: 39 | 1. Once the methods are mapped from the `srcObj` to the `dstObj`, the method calls will 40 | not get "routed" through `__getattr__` method (if implemented) in `type(dstObj)` class. 41 | 2. An example of what a 'key' and 'value' look like: 42 | key: MakeSequential 43 | value: > 45 | """ 46 | # prevent methods that we intend to specialize from being mapped. The specialized 47 | # (overridden) methods are methods with the same name as the corresponding method in 48 | # the source ZOS API COM object written for each ZOS API COM object in an associated 49 | # python script such as i_analyses_methods.py for I_Analyses 50 | overridden_methods = get_callable_method_dict(type(dstObj)).keys() 51 | #overridden_attrs = [each for each in type(dstObj).__dict__.keys() if not each.startswith('_')] 52 | # 53 | 54 | def zos_wrapper_deco(func): 55 | def wrapper(*args, **kwargs): 56 | return wrapped_zos_object(func(*args, **kwargs)) 57 | varnames = func.im_func.func_code.co_varnames # alternative is to use inspect.getargspec 58 | params = [par for par in varnames if par not in ('self', 'ret')] # removes 'self' and 'ret' 59 | wrapper.__doc__ = func.im_func.func_name + '(' + ', '.join(params) + ')' 60 | return wrapper 61 | # 62 | for key, value in get_callable_method_dict(srcObj).items(): 63 | if key not in overridden_methods: 64 | setattr(dstObj, key, zos_wrapper_deco(value)) 65 | 66 | def get_properties(zos_obj): 67 | """Returns a lists of properties bound to the object `zos_obj` 68 | 69 | @param zos_obj: ZOS API Python COM object 70 | @return prop_get: list of properties that are only getters 71 | @return prop_set: list of properties that are both getters and setters 72 | """ 73 | prop_get = set(zos_obj._prop_map_get_.keys()) 74 | prop_set = set(zos_obj._prop_map_put_.keys()) 75 | if prop_set.issubset(prop_get): 76 | prop_get = prop_get.difference(prop_set) 77 | else: 78 | msg = 'Assumption all getters are also setters is incorrect!' 79 | raise NotImplementedError(msg) 80 | return list(prop_get), list(prop_set) 81 | 82 | #%% 83 | class ZOSPropMapper(object): 84 | """Descriptor for mapping ZOS object properties to corresponding wrapper classes 85 | """ 86 | def __init__(self, zos_interface_attr, property_name, setter=False, cast_to=None): 87 | """ 88 | @param zos_interface_attr : attribute used to dispatch method/property calls to 89 | the zos_object (it hold the zos_object) 90 | @param propname : string, like 'SystemName' for IOpticalSystem 91 | @param setter : if False, a read-only data descriptor is created 92 | @param cast_to : Name of class (generally the base class) whose property to call 93 | """ 94 | self.property_name = property_name # property_name is a string like 'SystemName' for IOpticalSystem 95 | self.zos_interface_attr = zos_interface_attr 96 | self.setter = setter 97 | self.cast_to = cast_to 98 | 99 | def __get__(self, obj, objtype): 100 | if self.cast_to: 101 | return wrapped_zos_object(getattr(_CastTo(obj.__dict__[self.zos_interface_attr], self.cast_to), self.property_name)) 102 | else: 103 | return wrapped_zos_object(getattr(obj.__dict__[self.zos_interface_attr], self.property_name)) 104 | 105 | def __set__(self, obj, value): 106 | if self.setter: 107 | if self.cast_to: 108 | setattr(_CastTo(obj.__dict__[self.zos_interface_attr], self.cast_to), self.property_name, value) 109 | else: 110 | setattr(obj.__dict__[self.zos_interface_attr], self.property_name, value) 111 | else: 112 | raise AttributeError("Can't set {}".format(self.property_name)) 113 | 114 | 115 | def managed_wrapper_class_factory(zos_obj): 116 | """Creates and returns a wrapper class of a ZOS object, exposing the ZOS objects 117 | methods and propertis, and patching custom specialized attributes 118 | 119 | @param zos_obj: ZOS API Python COM object 120 | """ 121 | cls_name = repr(zos_obj).split()[0].split('.')[-1] 122 | dispatch_attr = '_' + cls_name.lower() # protocol to be followed to store the ZOS COM object 123 | 124 | cdict = {} # class dictionary 125 | 126 | # patch the properties of the base objects 127 | base_cls_list = inheritance_dict.get(cls_name, None) 128 | if base_cls_list: 129 | for base_cls_name in base_cls_list: 130 | getters, setters = get_properties(_CastTo(zos_obj, base_cls_name)) 131 | for each in getters: 132 | exec("p{} = ZOSPropMapper('{}', '{}', cast_to='{}')".format(each, dispatch_attr, each, base_cls_name), globals(), cdict) 133 | for each in setters: 134 | exec("p{} = ZOSPropMapper('{}', '{}', setter=True, cast_to='{}')".format(each, dispatch_attr, each, base_cls_name), globals(), cdict) 135 | 136 | # patch the property attributes of the given ZOS object 137 | getters, setters = get_properties(zos_obj) 138 | for each in getters: 139 | exec("p{} = ZOSPropMapper('{}', '{}')".format(each, dispatch_attr, each), globals(), cdict) 140 | for each in setters: 141 | exec("p{} = ZOSPropMapper('{}', '{}', setter=True)".format(each, dispatch_attr, each), globals(), cdict) 142 | 143 | def __init__(self, zos_obj): 144 | 145 | # dispatcher attribute 146 | cls_name = repr(zos_obj).split()[0].split('.')[-1] 147 | dispatch_attr = '_' + cls_name.lower() # protocol to be followed to store the ZOS COM object 148 | self.__dict__[dispatch_attr] = zos_obj 149 | self._dispatch_attr_value = dispatch_attr # used in __getattr__ 150 | 151 | # Store base class object 152 | self._base_cls_list = inheritance_dict.get(cls_name, None) 153 | 154 | # patch the methods of the base class(s) of the given ZOS object 155 | if self._base_cls_list: 156 | for base_cls_name in self._base_cls_list: 157 | replicate_methods(_CastTo(zos_obj, base_cls_name), self) 158 | 159 | # patch the methods of given ZOS object 160 | replicate_methods(zos_obj, self) 161 | 162 | # mark object as wrapped to prevent it from being wrapped subsequently 163 | self._wrapped = True 164 | 165 | # Provide a way to make property calls without the prefix p 166 | def __getattr__(self, attrname): 167 | return wrapped_zos_object(getattr(self.__dict__[self._dispatch_attr_value], attrname)) 168 | 169 | def __repr__(self): 170 | if type(self).__name__ == 'IZOSAPI_Application': 171 | repr_str = "{.__name__}(NumberOfOpticalSystems = {})".format(type(self), self.pNumberOfOpticalSystems) 172 | else: 173 | repr_str = "{.__name__}".format(type(self)) 174 | return repr_str 175 | 176 | cdict['__init__'] = __init__ 177 | cdict['__getattr__'] = __getattr__ 178 | cdict['__repr__'] = __repr__ 179 | 180 | # patch custom methods from python files imported as modules 181 | module_import_str = """ 182 | try: 183 | from pyzos.zos_obj_override.{module:} import * 184 | except ImportError: 185 | pass 186 | """.format(module=cls_name.lower() + '_methods') 187 | exec(module_import_str, globals(), cdict) 188 | 189 | _ = cdict.pop('print_function', None) 190 | _ = cdict.pop('division', None) 191 | 192 | return type(cls_name, (), cdict) 193 | 194 | def wrapped_zos_object(zos_obj): 195 | """Helper function to wrap ZOS API COM objects. 196 | 197 | @param zos_obj : ZOS API Python COM object 198 | @return: instance of the wrapped ZOS API class. If the input object is not a ZOS-API 199 | COM object or if it is already wrapped, then the object is returned without 200 | wrapping. 201 | 202 | Notes: 203 | The function dynamically creates a wrapped class with all the provided methods, 204 | properties, and custom methods monkey patched; and returns an instance of it. 205 | """ 206 | if hasattr(zos_obj, '_wrapped') or ('CLSID' not in dir(zos_obj)): 207 | return zos_obj 208 | else: 209 | Class = managed_wrapper_class_factory(zos_obj) 210 | return Class(zos_obj) 211 | 212 | #%% ZOS object inheritance relationships dictionary 213 | # Unfortunately this dict is created manually following the ZOS-API documentation. There 214 | # is no way to know this relationship querying the pythoncom objects. 215 | # Rules (and assumptions made by functions using this dict): 216 | # 1. The base class hierarchy is encoded as lists (i.e. elements are ordered) in the value fields of the dict 217 | # 2. The dict only contain those ZOS objects that have one or more parent classes. i.e. empty lists are not 218 | # allowed. 219 | # 3. The order of super classes in each list: [immediate-base-cls, next-level-base-cls, ..., top-most-base-cls] 220 | inheritance_dict = { 221 | ## IEditor Interface - base interface for all 5 editors 222 | 'ILensDataEditor' : ['IEditor',], 223 | 'IMultiConfigEditor' : ['IEditor',], 224 | 'IMeritFunctionEditor' : ['IEditor',], 225 | 'INonSeqEditor' : ['IEditor',], 226 | 'IToleranceDataEditor' : ['IEditor',], 227 | ## IAS_ Interface - base class for all analysis settings interfaces 228 | # Aberrations interface settings 229 | 'IAS_FieldCurvatureAndDistortion' : ['IAS_',], 230 | 'IAS_FocalShiftDiagram' : ['IAS_',], 231 | 'IAS_GridDistortion' : ['IAS_',], 232 | 'IAS_LateralColor' : ['IAS_',], 233 | 'IAS_LongitudinalAberration' : ['IAS_',], 234 | 'IAS_RayTrace' : ['IAS_',], 235 | 'IAS_SeidelCoefficients' : ['IAS_',], 236 | 'IAS_SeidelDiagram' : ['IAS_',], 237 | 'IAS_ZernikeAnnularCoefficients' : ['IAS_',], 238 | 'IAS_ZernikeCoefficientsVsField' : ['IAS_',], 239 | 'IAS_ZernikeFringeCoefficients' : ['IAS_',], 240 | 'IAS_ZernikeStandardCoefficients' : ['IAS_',], 241 | # EncircledEnergy interface settings 242 | 'IAS_DiffractionEncircledEnergy' : ['IAS_',], 243 | 'IAS_ExtendedSourceEncircledEnergy' : ['IAS_',], 244 | 'IAS_GeometricEncircledEnergy' : ['IAS_',], 245 | 'IAS_GeometricLineEdgeSpread' : ['IAS_',], 246 | # Fans interface settings 247 | 'IAS_Fan' : ['IAS_',], 248 | # Mtf interface settings 249 | 'IAS_FftMtf' : ['IAS_',], 250 | 'IAS_FftMtfMap' : ['IAS_',], 251 | 'IAS_FftMtfvsField' : ['IAS_',], 252 | 'IAS_FftSurfaceMtf' : ['IAS_',], 253 | 'IAS_FftThroughFocusMtf' : ['IAS_',], 254 | 'IAS_GeometricMtf' : ['IAS_',], 255 | 'IAS_GeometricMtfMap' : ['IAS_',], 256 | 'IAS_GeometricMtfvsField' : ['IAS_',], 257 | 'IAS_GeometricThroughFocusMtf' : ['IAS_',], 258 | 'IAS_HuygensMtf' : ['IAS_',], 259 | 'IAS_HuygensMtfvsField' : ['IAS_',], 260 | 'IAS_HuygensSurfaceMtf' : ['IAS_',], 261 | 'IAS_HuygensThroughFocusMtf' : ['IAS_',], 262 | # Psf interface settings 263 | 'IAS_FftPsf' : ['IAS_',], 264 | 'IAS_FftPsfCrossSection' : ['IAS_',], 265 | 'IAS_FftPsfLineEdgeSpread' : ['IAS_',], 266 | 'IAS_HuygensPsf' : ['IAS_',], 267 | 'IAS_HuygensPsfCrossSection' : ['IAS_',], 268 | # RayTracing interface settings 269 | 'IAS_DetectorViewer' : ['IAS_',], 270 | # RMS interface settings 271 | 'IAS_RMSField' : ['IAS_',], 272 | 'IAS_RMSFieldMap' : ['IAS_',], 273 | 'IAS_RMSFocus' : ['IAS_',], 274 | 'IAS_RMSLambdaDiagram' : ['IAS_',], 275 | # Spot interface settings 276 | 'IAS_Spot' : ['IAS_',], 277 | # Surface interface settings 278 | 'IAS_SurfaceCurvature' : ['IAS_',], 279 | 'IAS_SurfaceCurvatureCross' : ['IAS_',], 280 | 'IAS_SurfacePhase' : ['IAS_',], 281 | 'IAS_SurfacePhaseCross' : ['IAS_',], 282 | 'IAS_SurfaceSag' : ['IAS_',], 283 | 'IAS_SurfaceSagCross' : ['IAS_',], 284 | # Wavefront interface settings 285 | 'IAS_Foucault' : ['IAS_',], 286 | ## IOpticalSystemTools Interface - base class for all system tools 287 | 'IBatchRayTrace' : ['ISystemTool',], 288 | 'IConvertToNSCGroup' : ['ISystemTool',], 289 | 'ICreateArchive' : ['ISystemTool',], 290 | 'IExportCAD' : ['ISystemTool',], 291 | 'IGlobalOptimization' : ['ISystemTool',], 292 | 'IHammerOptimization' : ['ISystemTool',], 293 | 'ILensCatalogs' : ['ISystemTool',], 294 | 'ILightningTrace' : ['ISystemTool',], 295 | 'ILocalOptimization' : ['ISystemTool',], 296 | 'IMFCalculator' : ['ISystemTool',], 297 | 'INSCRayTrace' : ['ISystemTool',], 298 | 'IQuickAdjust' : ['ISystemTool',], 299 | 'IQuickFocus' : ['ISystemTool',], 300 | 'IRestoreArchive' : ['ISystemTool',], 301 | 'IScale' : ['ISystemTool',], 302 | 'ITolerancing' : ['ISystemTool',], 303 | ## IWizard Interface - base interface for all wizards 304 | 'INSCWizard' : ['IWizard',], 305 | 'INSCBitmapWizard' : ['INSCWizard', 'IWizard',], 306 | 'INSCOptimizationWizard' : ['INSCWizard', 'IWizard',], 307 | 'INSCRoadwayLightingWizard' : ['IWizard',], 308 | 'IToleranceWizard' : ['IWizard',], 309 | 'INSCToleranceWizard': ['IToleranceWizard', 'IWizard',], 310 | 'ISEQToleranceWizard' : ['IToleranceWizard', 'IWizard',], 311 | 'ISEQOptimizationWizard' : ['IWizard',], 312 | } 313 | # Ensure Rule #2 of inheritance_dict. 314 | for each in inheritance_dict.values(): 315 | assert len(each), 'Empty base class list not allowed in inheritance_dict' -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # Name: setup.py 3 | # Purpose: Standard module installation script 4 | # Licence: MIT License 5 | # This file is subject to the terms and conditions of the MIT License. 6 | # For further details, please refer to LICENSE.txt 7 | #------------------------------------------------------------------------------- 8 | """ 9 | This script will install the PyZOS library into your Lib/site-packages directory 10 | as a standard Python module. It can then be imported like any other module package. 11 | """ 12 | 13 | from setuptools import setup, find_packages 14 | 15 | with open('README.rst') as fh: 16 | long_description = fh.read() 17 | 18 | setup( 19 | name='PyZOS', 20 | version='0.0.8', 21 | description='Python interface for Zemax OpticStudio COM API (ZOS-API)', 22 | long_description=long_description, 23 | author='Indranil Sinharoy', 24 | author_email='indranil_leo@yahoo.com', 25 | license='MIT', 26 | keywords='zemax opticstudio extensions COM optics ZOS-API', 27 | url='https://github.com/pyzos/pyzos', 28 | packages=find_packages(), 29 | include_package_data=True, 30 | classifiers=[ 31 | 'Intended Audience :: Science/Research', 32 | 'Topic :: Scientific/Engineering', 33 | 'Natural Language :: English', 34 | 'Environment :: Win32 (MS Windows)', 35 | 'License :: OSI Approved :: MIT License', 36 | 'Operating System :: Microsoft :: Windows :: Windows 7', 37 | 'Programming Language :: Python', 38 | 'Programming Language :: Python :: 2.7', 39 | 'Programming Language :: Python :: 3', 40 | 'Programming Language :: Python :: 3.2', 41 | 'Programming Language :: Python :: 3.3', 42 | 'Programming Language :: Python :: 3.4', 43 | 'Programming Language :: Python :: 3.5', 44 | 'Topic :: Scientific/Engineering', 45 | ], 46 | ) 47 | --------------------------------------------------------------------------------