├── pyzdde ├── __init__.py ├── utils │ ├── __init__.py │ └── pyzddeutils.py ├── zcodes │ ├── __init__.py │ └── zemaxbuttons.py ├── convert.exe ├── emfplus.exe ├── arraytrace │ ├── Release │ │ └── ArrayTrace.dll │ ├── x64 │ │ └── Release │ │ │ └── ArrayTrace.dll │ ├── arrayTraceClient.h │ ├── ArrayDemo.c │ ├── arrayTraceClient.c │ └── zclient.c ├── settings.ini ├── settings.ini-dist ├── config.py ├── systems.py ├── misc.py └── ddeclient.py ├── ZPL17.ZPL ├── dist └── Start.bat ├── AHK ├── AutoHotkey.chm ├── AutoHotkeyA32.exe ├── AutoHotkeyU32.exe ├── AutoHotkeyU64.exe ├── Compiler │ ├── Ahk2Exe.exe │ ├── ANSI 32-bit.bin │ ├── Unicode 32-bit.bin │ ├── Unicode 64-bit.bin │ └── readme.txt ├── Template.ahk ├── WindowSpy.ahk └── license.txt ├── IPeG_Logo_kurz.jpg ├── img ├── Settings_Opti.png └── User_Interface_Opti_2.jpg ├── requirements.txt ├── BUILD_PORTABLE.txt ├── config.ini ├── Convert_to_NSGroup.ahk ├── optimize.ahk ├── ray_trace.ahk ├── Export_to_CAD_10.ahk ├── Export_to_CAD_7.ahk ├── main.spec ├── README.md ├── help.ui ├── settings.ui ├── gui.ui └── main.py /pyzdde/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyzdde/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyzdde/zcodes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ZPL17.ZPL: -------------------------------------------------------------------------------- 1 | value = PVHX() 2 | OPTRETURN 0, value 3 | -------------------------------------------------------------------------------- /dist/Start.bat: -------------------------------------------------------------------------------- 1 | cd main 2 | Start /b main.exe 3 | pause -------------------------------------------------------------------------------- /AHK/AutoHotkey.chm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/AHK/AutoHotkey.chm -------------------------------------------------------------------------------- /IPeG_Logo_kurz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/IPeG_Logo_kurz.jpg -------------------------------------------------------------------------------- /pyzdde/convert.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/pyzdde/convert.exe -------------------------------------------------------------------------------- /pyzdde/emfplus.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/pyzdde/emfplus.exe -------------------------------------------------------------------------------- /AHK/AutoHotkeyA32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/AHK/AutoHotkeyA32.exe -------------------------------------------------------------------------------- /AHK/AutoHotkeyU32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/AHK/AutoHotkeyU32.exe -------------------------------------------------------------------------------- /AHK/AutoHotkeyU64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/AHK/AutoHotkeyU64.exe -------------------------------------------------------------------------------- /img/Settings_Opti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/img/Settings_Opti.png -------------------------------------------------------------------------------- /AHK/Compiler/Ahk2Exe.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/AHK/Compiler/Ahk2Exe.exe -------------------------------------------------------------------------------- /AHK/Compiler/ANSI 32-bit.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/AHK/Compiler/ANSI 32-bit.bin -------------------------------------------------------------------------------- /img/User_Interface_Opti_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/img/User_Interface_Opti_2.jpg -------------------------------------------------------------------------------- /AHK/Compiler/Unicode 32-bit.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/AHK/Compiler/Unicode 32-bit.bin -------------------------------------------------------------------------------- /AHK/Compiler/Unicode 64-bit.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/AHK/Compiler/Unicode 64-bit.bin -------------------------------------------------------------------------------- /pyzdde/arraytrace/Release/ArrayTrace.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/pyzdde/arraytrace/Release/ArrayTrace.dll -------------------------------------------------------------------------------- /pyzdde/arraytrace/x64/Release/ArrayTrace.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibib/Opti_2/HEAD/pyzdde/arraytrace/x64/Release/ArrayTrace.dll -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pandas 2 | git+git://github.com/indranilsinharoy/PyZDDE 3 | git+git://github.com/pyinstaller/pyinstaller 4 | PyQt5 5 | psutil 6 | pywin32 7 | numpy -------------------------------------------------------------------------------- /pyzdde/settings.ini: -------------------------------------------------------------------------------- 1 | [zemax_text_encoding] 2 | encoding = unicode 3 | 4 | [zemax_file_encoding] 5 | encoding = unicode 6 | 7 | [imageMagick_config] 8 | useinstalled = False 9 | imgmagickiinstalldir = C:\Program Files\ImageMagick-6.8.9-Q8 10 | 11 | -------------------------------------------------------------------------------- /BUILD_PORTABLE.txt: -------------------------------------------------------------------------------- 1 | Run in Anaconda context: 2 | pyinstaller -D --clean main.spec 3 | 4 | Pyinstaller can be installed with: 5 | pip install git+https://github.com/pyinstaller/pyinstaller 6 | 7 | Pyinstaller Documentation: 8 | http://www.pyinstaller.org/ 9 | -------------------------------------------------------------------------------- /pyzdde/settings.ini-dist: -------------------------------------------------------------------------------- 1 | [zemax_text_encoding] 2 | encoding = unicode 3 | 4 | [zemax_file_encoding] 5 | encoding = unicode 6 | 7 | [imageMagick_config] 8 | useinstalled = False 9 | imgmagickiinstalldir = C:\Program Files\ImageMagick-6.8.9-Q8 10 | 11 | -------------------------------------------------------------------------------- /AHK/Template.ahk: -------------------------------------------------------------------------------- 1 | #NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases. 2 | ; #Warn ; Enable warnings to assist with detecting common errors. 3 | SendMode Input ; Recommended for new scripts due to its superior speed and reliability. 4 | SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory. 5 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | workingdir = C:/Users/Jannes/OneDrive/IPEG/Peer/zemax_gui 3 | 4 | [ZEMAX] 5 | zemaxexec = C:/Program Files/Zemax OpticStudio/OpticStudio.exe 6 | 7 | [INVENTOR] 8 | inventorexec = C:/Program Files/Autodesk/Inventor 2017/Bin/Inventor.exe 9 | 10 | [RAYFIELD] 11 | one = dummy.dat 12 | one_checked = False 13 | two = dummy.dat 14 | two_checked = False 15 | three = 16 | three_checked = False 17 | four = 18 | four_checked = False 19 | 20 | -------------------------------------------------------------------------------- /Convert_to_NSGroup.ahk: -------------------------------------------------------------------------------- 1 | String := 1 2 | OutVar := "" 3 | StringTrimLeft, OutVar, String, 1 4 | 5 | WinMaximize, Zemax OpticStudio 6 | sleep, 700 7 | WinActivate, Zemax OpticStudio 8 | sleep, 700 9 | WinSet, Top 10 | sleep, 700 11 | 12 | Send ^+T ; Ctrl + Shift + T 13 | sleep, 1000 14 | 15 | Loop 6 { 16 | send `t ;Tab 17 | sleep, 100 18 | } 19 | 20 | send ^a ;Ctrl + a 21 | send `b ;Backspace 22 | send {raw}%1% 23 | 24 | Send `n ;Enter 25 | sleep, 20 26 | Send `n ;Enter 27 | Send {NumpadAdd} 28 | -------------------------------------------------------------------------------- /optimize.ahk: -------------------------------------------------------------------------------- 1 | String := 1 2 | OutVar := "" 3 | StringTrimLeft, OutVar, String, 1 4 | 5 | WinMaximize, Zemax OpticStudio 6 | sleep, 700 7 | WinActivate, Zemax OpticStudio 8 | sleep, 700 9 | WinSet, Top 10 | sleep, 700 11 | 12 | Send ^+O ; Ctrl + Shift + O 13 | sleep, 1000 14 | 15 | send `t ;Tab 16 | send `t ;Tab 17 | send d ;D 18 | 19 | send `t ;Tab 20 | send `t ;Tab 21 | 22 | send {End} 23 | send {Up} 24 | send {Up} 25 | 26 | send `t ;Tab 27 | send {NumpadAdd} 28 | send `t ;Tab 29 | 30 | send `n ;Enter 31 | -------------------------------------------------------------------------------- /ray_trace.ahk: -------------------------------------------------------------------------------- 1 | String := 1 2 | OutVar := "" 3 | StringTrimLeft, OutVar, String, 1 4 | 5 | WinMaximize, Zemax OpticStudio 6 | sleep, 700 7 | WinActivate, Zemax OpticStudio 8 | sleep, 700 9 | WinSet, Top 10 | sleep, 700 11 | 12 | Send ^d ; Ctrl + d 13 | sleep, 1000 14 | 15 | 16 | ;To clear_n_trace 17 | Loop 7 { 18 | send `t ;Tab 19 | sleep, 100 20 | } 21 | 22 | send `t ;Tab 23 | send {NumpadAdd} 24 | send `t ;Tab 25 | send {NumpadAdd} 26 | send `t ;Tab 27 | send {NumpadAdd} 28 | 29 | send `n ;Enter, Start the tracing -------------------------------------------------------------------------------- /Export_to_CAD_10.ahk: -------------------------------------------------------------------------------- 1 | String := 1 2 | OutVar := "" 3 | StringTrimLeft, OutVar, String, 1 4 | 5 | WinMaximize, Zemax OpticStudio 6 | sleep, 700 7 | WinActivate, Zemax OpticStudio 8 | sleep, 700 9 | WinSet, Top 10 | sleep, 700 11 | 12 | Send ^+Y ; Ctrl + Shift + Y 13 | sleep, 1000 14 | 15 | ;To spline segments 16 | Loop 3 { 17 | send `t ;Tab 18 | sleep, 100 19 | } 20 | 21 | send 128 ;Select Spline segments 22 | send `t ;Tab 23 | 24 | ;To Tolerance 25 | Loop 3 { 26 | send `t ;Tab 27 | sleep, 100 28 | } 29 | 30 | send {End} ;Waehle letzte Toleranz 31 | 32 | send `n ;Enter, bestaetigen der Konfiguaration 33 | sleep, 100 34 | 35 | ;To Path 36 | Loop 6 { 37 | send `t ;Tab 38 | sleep, 100 39 | } 40 | 41 | send {Space} 42 | send {raw}%1% 43 | send `n 44 | 45 | ;To Filename 46 | Loop 6 { 47 | send `t ;Tab 48 | sleep, 100 49 | } 50 | 51 | send {raw}%2% 52 | send `n ;Enter => Save 53 | 54 | sleep, 20 55 | send `n ;File already exists -------------------------------------------------------------------------------- /Export_to_CAD_7.ahk: -------------------------------------------------------------------------------- 1 | String := 1 2 | OutVar := "" 3 | StringTrimLeft, OutVar, String, 1 4 | 5 | WinMaximize, Zemax OpticStudio 6 | sleep, 700 7 | WinActivate, Zemax OpticStudio 8 | sleep, 700 9 | WinSet, Top 10 | sleep, 700 11 | 12 | Send ^+Y ; Ctrl + Shift + Y 13 | sleep, 1000 14 | 15 | ;To spline segments 16 | Loop 3 { 17 | send `t ;Tab 18 | sleep, 100 19 | } 20 | 21 | send 128 ;Select Spline segments 22 | send `t ;Tab 23 | 24 | ;To Tolerance 25 | Loop 3 { 26 | send `t ;Tab 27 | sleep, 100 28 | } 29 | 30 | send {End} ;Waehle letzte Toleranz 31 | 32 | send `n ;Enter, bestaetigen der Konfiguaration 33 | sleep, 100 34 | 35 | ;To Path 36 | Loop 5 { 37 | send `t ;Tab 38 | sleep, 100 39 | } 40 | 41 | send {Space} 42 | send {raw}%1% 43 | send `n 44 | 45 | ;To Filename 46 | Loop 6 { 47 | send `t ;Tab 48 | sleep, 100 49 | } 50 | 51 | send {raw}%2% 52 | send `n ;Enter => Save 53 | 54 | sleep, 20 55 | send `n ;File already exists -------------------------------------------------------------------------------- /pyzdde/arraytrace/arrayTraceClient.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define DLL_EXPORT __declspec(dllexport) 9 | 10 | #define WM_USER_INITIATE (WM_USER + 1) 11 | #define DDE_TIMEOUT 50000 12 | #pragma warning ( disable : 4996 ) // functions like strcpy are now deprecated for security reasons; this disables the warning 13 | 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | typedef struct 19 | { 20 | double x, y, z, l, m, n, opd, intensity; 21 | double Exr, Exi, Eyr, Eyi, Ezr, Ezi; 22 | int wave, error, vigcode, want_opd; 23 | }DDERAYDATA; 24 | 25 | LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 26 | void WaitForData(HWND hwnd); 27 | char *GetString(char *szBuffer, int n, char *szSubString); 28 | int PostArrayTraceMessage(char *szBuffer, DDERAYDATA *RD); 29 | void rayTraceFunction(); 30 | DLL_EXPORT int __stdcall arrayTrace(DDERAYDATA * pRAD, unsigned int timeout); 31 | 32 | #ifdef __cplusplus 33 | } 34 | #endif 35 | -------------------------------------------------------------------------------- /main.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | block_cipher = None 4 | 5 | 6 | added_files = [ 7 | ( 'gui.ui', '.'), 8 | ( 'config.ini', '.' ), 9 | ( 'settings.ui', '.'), 10 | ( 'pyzdde', 'pyzdde'), 11 | ( 'AHK', 'AHK'), 12 | ( 'Convert_to_NSGroup.ahk', '.'), 13 | ( 'help.ui', '.' ), 14 | ( 'optimize.ahk', '.' ), 15 | ( 'Export_to_CAD_7.ahk', '.' ), 16 | ( 'Export_to_CAD_10.ahk', '.' ), 17 | ( 'ray_trace.ahk', '.' ) 18 | ] 19 | 20 | a = Analysis(['main.py'], 21 | pathex=['C:\\Users\\Jannes_S5\\Dropbox\\IPeG\\zemax_gui'], 22 | binaries=[], 23 | datas=added_files, 24 | hiddenimports=['pyzdde'], 25 | hookspath=[], 26 | runtime_hooks=[], 27 | excludes=[], 28 | win_no_prefer_redirects=False, 29 | win_private_assemblies=False, 30 | cipher=block_cipher, 31 | noarchive=False) 32 | pyz = PYZ(a.pure, a.zipped_data, 33 | cipher=block_cipher) 34 | exe = EXE(pyz, 35 | a.scripts, 36 | [], 37 | exclude_binaries=True, 38 | name='main', 39 | debug=False, 40 | bootloader_ignore_signals=False, 41 | strip=False, 42 | upx=True, 43 | console=True ) 44 | coll = COLLECT(exe, 45 | a.binaries, 46 | a.zipfiles, 47 | a.datas, 48 | strip=False, 49 | upx=True, 50 | name='main') 51 | -------------------------------------------------------------------------------- /AHK/Compiler/readme.txt: -------------------------------------------------------------------------------- 1 | ==================================================== 2 | Important Notes for Ahk2Exe for AutoHotkey 1.1 3 | ==================================================== 4 | 5 | 1. BIN Files 6 | 7 | Scripts are "compiled" by combining them with a .bin file containing 8 | the script interpreter, similar to AutoHotkey.exe. This version of 9 | Ahk2Exe requires a .bin file compiled from AutoHotkey 1.1 source code 10 | or a compatible fork. The official download includes three: 11 | 12 | - ANSI 32-bit.bin 13 | - Unicode 32-bit.bin 14 | - Unicode 64-bit.bin 15 | 16 | One of these should be selected from the "Base File" drop-down list 17 | in the GUI. If "(Default)" is selected, there must be a file named 18 | AutoHotkeySC.bin in the Ahk2Exe directory. This is normally created 19 | by the AutoHotkey installer (simply a copy of another bin file). 20 | If this file does not exist, Ahk2Exe will attempt to copy it from 21 | one of the other bin files (typically Unicode 32-bit). 22 | 23 | 24 | 2. AutoHotkey.exe 25 | 26 | Function library auto-includes are only supported if a copy of 27 | AutoHotkey.exe exists in the parent directory. For example: 28 | 29 | C:\Program Files\AutoHotkey\Compiler\Ahk2Exe.exe 30 | C:\Program Files\AutoHotkey\AutoHotkey.exe 31 | 32 | Although this exe doesn't need to be the same version as the current 33 | .bin file, it must at least be able to load (not run) the script you 34 | are attempting to compile. 35 | 36 | 37 | 3. Usage 38 | 39 | Usage via the GUI should be self-explanatory. For command-line 40 | usage, run "Ahk2Exe.exe /?". For more info, see the documentation: 41 | 42 | Offline: See "ahk2exe" in the AutoHotkey_L help file index. 43 | Online: https://autohotkey.com/docs/Scripts.htm#ahk2exe 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [DOI: 10.5281/zenodo.3248687](https://zenodo.org/record/3248687) 2 | 3 | # Opti² Hybrid Raytracing 4 | 5 | Opti² is a tool to support optical simulation in the development and optimization of optical systems. Opti² was developed for the optics simulation software [Zemax OpticStudio®]( https://www.zemax.com/products/opticstudio) version 18.4 to implement a hybrid raytracing method using [PyZDDE](https://github.com/xzos/PyZDDE). The method is based on coupling sequential and non-sequential ray tracing, which combines the respective advantages of the two ray tracing programs in one simulation environment. In addition, Rayfiles can be deposited in Opti² in the settings area, which are automatically inserted into the non-sequential model when the sequential model is converted into the non-sequential model. By executing a raytrace in the non-sequential mode, the luminous flux determined on the detector is transferred to the enclosed *.ZPL macro for the optimization of the system. This macro enables the optimization of the optical system with regard to efficiency and serves as an example. Programming and including own macros into Opti², allows user-defined optimization of optical systems. 6 | 7 | ### Prerequisites 8 | 9 | Install the listed requirements of the requirements.txt in your Python enviroment. Tested with Anaconda and Python 3.5. 10 | Insert the *.ZPL macro in the macro folder. 11 | 12 | ### Starting 13 | 14 | Execute the main.py. 15 | 16 | ## Build portable 17 | 18 | Install Pyinstaller in your enviroment and run the build.bat. When the build process is finished execute ./dist/Start.bat. 19 | It does not work if QT ist installed over Anaconda. Instead you can use the pip source. 20 | 21 | ## Screenshots 22 | 23 | Main UI 24 | 25 | Settings menu 26 | 27 | ## Program Authors 28 | 29 | * **J. August** 30 | * **P.-P. Ley** 31 | 32 | ## License 33 | 34 | This project is licensed under the GPL License - see the [LICENSE](LICENSE) file for details 35 | 36 | ## Special Acknowledgments 37 | 38 | * This tool was developed within the framework of the "[Wege in die Forschung II]( https://www.uni-hannover.de/de/forschung/wiss-nachwuchs/postdocs/bisher-gefoerderte-projekte/wif-ii-projekte-2017/wolf/)" funding programme for young researchers at Leibniz Universität Hannover. 39 | * [Institute of Product Development](https://www.ipeg.uni-hannover.de/institut.html?&L=1) 40 | * [PyZDDE](https://github.com/xzos/PyZDDE) 41 | -------------------------------------------------------------------------------- /help.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 409 10 | 296 11 | 12 | 13 | 14 | Help 15 | 16 | 17 | 18 | 19 | 30 20 | 30 21 | 341 22 | 241 23 | 24 | 25 | 26 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 27 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 28 | p, li { white-space: pre-wrap; } 29 | </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> 30 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Allegemeine Hilfe</p> 31 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> 32 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Vor der ersten Nutzung muss in Zemax OpticStudio folgende Konfiguration, zur Vermeidung von Fehlern vorgenommen werden:</p> 33 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> 34 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Zemax =&gt; Project Preferences</p> 35 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Editors =&gt; Allow Extensions to Push Lenses (Haken setzen)</p> 36 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Shortcut Keys =&gt; unter &quot;available shortcuts&quot; =&gt; CAD-Files mit &quot;Ctrl + Shift + Y &quot; belegen</p> 37 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">und</p> 38 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Convert to NSC Group mit &quot;Ctrl + Shift + T&quot; belegen</p> 39 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /pyzdde/arraytrace/ArrayDemo.c: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /* The next few lines are standard "boiler plate" code all UDF's have */ 3 | /**************************************************************************/ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /* boiler plate declarations for functions in zclient we will call */ 13 | typedef struct 14 | { 15 | double x, y, z, l, m, n, opd, intensity; 16 | double Exr, Exi, Eyr, Eyi, Ezr, Ezi; 17 | int wave, error, vigcode, want_opd; 18 | }DDERAYDATA; 19 | 20 | void WaitForData(HWND hwnd); 21 | char *GetString(char *szBuffer, int n, char *szSubString); 22 | void remove_quotes(char *s); 23 | void PostRequestMessage(char *szItem, char *szDataOut); 24 | void PostArrayTraceMessage(char *szBuffer, DDERAYDATA *RD); 25 | void CenterWindow(HWND hwnd); 26 | void UserFunction(char *szCommandLine); 27 | void MakeEmptyWindow(int text, char *szAppName, char *szOptions); 28 | void Get_2_5_10(double cmax, double *cscale); 29 | 30 | /* boiler plate global variables used by the client code */ 31 | extern HINSTANCE globalhInstance; 32 | extern HWND hwndClient; 33 | 34 | 35 | /**************************************************/ 36 | /* Below is the code specific to this UDF program */ 37 | /**************************************************/ 38 | 39 | // ArrayDemo sample program 40 | // Written by Kenneth Moore March 1999 41 | // Version 1.0 42 | 43 | // prints intensity modified by user defined surfaces - June 1999 44 | 45 | // This sample program illustrates using ZCLIENT calls to trace large numbers 46 | // of rays with the GetTRaceArray family of DDE calls. It is really easy to do! 47 | // Most of this code is just for defining the rays to trace and printing the results. 48 | 49 | 50 | /* okay, now the actual code that creates the UDF data for display in a ZEMAX window */ 51 | /* this function MUST be called UserFunction and take the command line argument */ 52 | 53 | void UserFunction(char *szCommandLine) 54 | { 55 | char szModuleName[300], szOutputFile[300]; 56 | FILE *output; 57 | int i, j, k, show_settings; 58 | static char szBuffer[5000], szSub[256], szAppName[] = "Array Demo"; 59 | DDERAYDATA RD[1000]; 60 | 61 | /* extract the command line arguments */ 62 | show_settings = atoi(GetString(szCommandLine, 1, szSub)); 63 | /* this tells us where to put the output data */ 64 | GetString(szCommandLine, 2, szOutputFile); 65 | remove_quotes(szOutputFile); 66 | 67 | //MessageBox(hwndClient, szCommandLine, "szCommandLine:", MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL); 68 | //MessageBox(hwndClient, szOutputFile, "Output file:", MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL); 69 | 70 | /* first, update the lens so we have the latest data; and then test to make sure the system is valid */ 71 | PostRequestMessage("GetUpdate", szBuffer); 72 | 73 | if (atoi(GetString(szBuffer, 0, szSub))) 74 | { 75 | /* the current lens cannot be traced! */ 76 | /* some features may be able to create data without tracing rays; some can't */ 77 | /* If data cannot be created return "empty" data displays */ 78 | sprintf(szBuffer,"???"); 79 | MakeEmptyWindow(1, szAppName, szBuffer); 80 | return; 81 | } 82 | 83 | if (show_settings) MessageBox (hwndClient, "This window has no options.", "ZEMAX Client Window", MB_ICONEXCLAMATION | MB_OK | MB_SYSTEMMODAL); 84 | 85 | /* Fill RD with data to trace some rays */ 86 | RD[0].x = 0.0; 87 | RD[0].y = 0.0; 88 | RD[0].z = 0.0; 89 | RD[0].l = 0.0; 90 | RD[0].m = 0.0; 91 | RD[0].n = 0.0; 92 | RD[0].opd = 0.0; /* mode 0, like GetTrace */ 93 | RD[0].intensity = 0.0; 94 | RD[0].wave = 0; 95 | RD[0].error = 0; /* trace a bunch of rays */ 96 | RD[0].vigcode = 0; 97 | RD[0].want_opd = -1; 98 | 99 | /* define the rays */ 100 | k = 0; 101 | for (i = -10; i <= 10; i++) 102 | { 103 | for (j = -10; j <= 10; j++) 104 | { 105 | k++; 106 | RD[k].x = 0.0; 107 | RD[k].y = 0.0; 108 | RD[k].z = (double) i / 20.0; 109 | RD[k].l = (double) j / 20.0; 110 | RD[k].m = 0.0; 111 | RD[k].n = 0.0; 112 | RD[k].opd = 0.0; 113 | RD[k].intensity = 1.0; 114 | RD[k].wave = 1; 115 | RD[k].error = 0; 116 | RD[k].vigcode = 0; 117 | RD[k].want_opd = 0; 118 | } 119 | } 120 | RD[0].error = k; /* trace the k rays */ 121 | 122 | /* Now go get the data */ 123 | PostArrayTraceMessage(szBuffer, RD); 124 | /* Okay, we got the data! There, wasn't that easy! */ 125 | 126 | /* open a file for output */ 127 | if ((output = fopen(szOutputFile, "wt")) == NULL) 128 | { 129 | /* can't open the file!! */ 130 | return; 131 | } 132 | 133 | /* this windows function gives us the name of our own executable; we pass this back to ZEMAX */ 134 | GetModuleFileName(globalhInstance, szModuleName, 255); 135 | 136 | /* ok, make a text listing */ 137 | fputs("Listing of Array trace data\n",output); 138 | 139 | fputs(" px py error xout yout trans\n", output); 140 | 141 | k = 0; 142 | for (i = -10; i <= 10; i++) 143 | { 144 | for (j = -10; j <= 10; j++) 145 | { 146 | k++; 147 | 148 | sprintf(szBuffer, "%7.3f %7.3f %5i %15.6E %15.6E %7.4f\n", (double) i / 20.0, (double) j / 20.0, RD[k].error, RD[k].x, RD[k].y, RD[k].intensity); 149 | fputs(szBuffer, output); 150 | } 151 | } 152 | /* close the file! */ 153 | fclose(output); 154 | 155 | /* create a text window. Note we pass back the filename and module name. */ 156 | 157 | //MessageBox(hwndClient, szOutputFile, "Output file:", MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL); 158 | 159 | sprintf(szBuffer,"MakeTextWindow,\"%s\",\"%s\",\"%s\"", szOutputFile, szModuleName, szAppName); 160 | 161 | //MessageBox(hwndClient, szBuffer, "szBuffer:", MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL); 162 | 163 | PostRequestMessage(szBuffer, szBuffer); // insr: this statement sends the data back to Zemax for showing the text window 164 | // along with the previous sprintf(szBuffer, "Mak....) statement 165 | } 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /AHK/WindowSpy.ahk: -------------------------------------------------------------------------------- 1 | ; 2 | ; Window Spy 3 | ; 4 | 5 | #NoEnv 6 | #NoTrayIcon 7 | #SingleInstance Ignore 8 | SetWorkingDir, %A_ScriptDir% 9 | SetBatchLines, -1 10 | CoordMode, Pixel, Screen 11 | 12 | txtNotFrozen := "(Hold Ctrl or Shift to suspend updates)" 13 | txtFrozen := "(Updates suspended)" 14 | txtMouseCtrl := "Control Under Mouse Position" 15 | txtFocusCtrl := "Focused Control" 16 | 17 | Gui, New, hwndhGui AlwaysOnTop Resize MinSize 18 | Gui, Add, Text,, Window Title, Class and Process: 19 | Gui, Add, Checkbox, yp xp+200 w120 Right vCtrl_FollowMouse, Follow Mouse 20 | Gui, Add, Edit, xm w320 r3 ReadOnly -Wrap vCtrl_Title 21 | Gui, Add, Text,, Mouse Position: 22 | Gui, Add, Edit, w320 r4 ReadOnly vCtrl_MousePos 23 | Gui, Add, Text, w320 vCtrl_CtrlLabel, % txtFocusCtrl ":" 24 | Gui, Add, Edit, w320 r4 ReadOnly vCtrl_Ctrl 25 | Gui, Add, Text,, Active Window Position: 26 | Gui, Add, Edit, w320 r2 ReadOnly vCtrl_Pos 27 | Gui, Add, Text,, Status Bar Text: 28 | Gui, Add, Edit, w320 r2 ReadOnly vCtrl_SBText 29 | Gui, Add, Checkbox, vCtrl_IsSlow, Slow TitleMatchMode 30 | Gui, Add, Text,, Visible Text: 31 | Gui, Add, Edit, w320 r2 ReadOnly vCtrl_VisText 32 | Gui, Add, Text,, All Text: 33 | Gui, Add, Edit, w320 r2 ReadOnly vCtrl_AllText 34 | Gui, Add, Text, w320 r1 vCtrl_Freeze, % txtNotFrozen 35 | Gui, Show, NoActivate, Window Spy 36 | GetClientSize(hGui, temp) 37 | horzMargin := temp*96//A_ScreenDPI - 320 38 | SetTimer, Update, 250 39 | return 40 | 41 | GuiSize: 42 | Gui %hGui%:Default 43 | if !horzMargin 44 | return 45 | SetTimer, Update, % A_EventInfo=1 ? "Off" : "On" ; Suspend on minimize 46 | ctrlW := A_GuiWidth - horzMargin 47 | list = Title,MousePos,Ctrl,Pos,SBText,VisText,AllText,Freeze 48 | Loop, Parse, list, `, 49 | GuiControl, Move, Ctrl_%A_LoopField%, w%ctrlW% 50 | return 51 | 52 | Update: 53 | Gui %hGui%:Default 54 | GuiControlGet, Ctrl_FollowMouse 55 | CoordMode, Mouse, Screen 56 | MouseGetPos, msX, msY, msWin, msCtrl 57 | actWin := WinExist("A") 58 | if Ctrl_FollowMouse 59 | { 60 | curWin := msWin 61 | curCtrl := msCtrl 62 | WinExist("ahk_id " curWin) 63 | } 64 | else 65 | { 66 | curWin := actWin 67 | ControlGetFocus, curCtrl 68 | } 69 | WinGetTitle, t1 70 | WinGetClass, t2 71 | if (curWin = hGui || t2 = "MultitaskingViewFrame") ; Our Gui || Alt-tab 72 | { 73 | GuiControl,, Ctrl_Freeze, % txtFrozen 74 | return 75 | } 76 | GuiControl,, Ctrl_Freeze, % txtNotFrozen 77 | WinGet, t3, ProcessName 78 | GuiControl,, Ctrl_Title, % t1 "`nahk_class " t2 "`nahk_exe " t3 79 | CoordMode, Mouse, Relative 80 | MouseGetPos, mrX, mrY 81 | CoordMode, Mouse, Client 82 | MouseGetPos, mcX, mcY 83 | PixelGetColor, mClr, %msX%, %msY%, RGB 84 | mClr := SubStr(mClr, 3) 85 | GuiControl,, Ctrl_MousePos, % "Screen:`t" msX ", " msY " (less often used)`nWindow:`t" mrX ", " mrY " (default)`nClient:`t" mcX ", " mcY " (recommended)" 86 | . "`nColor:`t" mClr " (Red=" SubStr(mClr, 1, 2) " Green=" SubStr(mClr, 3, 2) " Blue=" SubStr(mClr, 5) ")" 87 | GuiControl,, Ctrl_CtrlLabel, % (Ctrl_FollowMouse ? txtMouseCtrl : txtFocusCtrl) ":" 88 | if (curCtrl) 89 | { 90 | ControlGetText, ctrlTxt, %curCtrl% 91 | cText := "ClassNN:`t" curCtrl "`nText:`t" textMangle(ctrlTxt) 92 | ControlGetPos cX, cY, cW, cH, %curCtrl% 93 | cText .= "`n`tx: " cX "`ty: " cY "`tw: " cW "`th: " cH 94 | WinToClient(curWin, cX, cY) 95 | ControlGet, curCtrlHwnd, Hwnd,, % curCtrl 96 | GetClientSize(curCtrlHwnd, cW, cH) 97 | cText .= "`nClient:`tx: " cX "`ty: " cY "`tw: " cW "`th: " cH 98 | } 99 | else 100 | cText := "" 101 | GuiControl,, Ctrl_Ctrl, % cText 102 | WinGetPos, wX, wY, wW, wH 103 | GetClientSize(curWin, wcW, wcH) 104 | GuiControl,, Ctrl_Pos, % "`tx: " wX "`ty: " wY "`tw: " wW "`th: " wH "`nClient:`tx: 0`ty: 0`tw: " wcW "`th: " wcH 105 | sbTxt := "" 106 | Loop 107 | { 108 | StatusBarGetText, ovi, %A_Index% 109 | if ovi = 110 | break 111 | sbTxt .= "(" A_Index "):`t" textMangle(ovi) "`n" 112 | } 113 | StringTrimRight, sbTxt, sbTxt, 1 114 | GuiControl,, Ctrl_SBText, % sbTxt 115 | GuiControlGet, bSlow,, Ctrl_IsSlow 116 | if bSlow 117 | { 118 | DetectHiddenText, Off 119 | WinGetText, ovVisText 120 | DetectHiddenText, On 121 | WinGetText, ovAllText 122 | } 123 | else 124 | { 125 | ovVisText := WinGetTextFast(false) 126 | ovAllText := WinGetTextFast(true) 127 | } 128 | GuiControl,, Ctrl_VisText, % ovVisText 129 | GuiControl,, Ctrl_AllText, % ovAllText 130 | return 131 | 132 | GuiClose: 133 | ExitApp 134 | 135 | WinGetTextFast(detect_hidden) 136 | { 137 | ; WinGetText ALWAYS uses the "fast" mode - TitleMatchMode only affects 138 | ; WinText/ExcludeText parameters. In Slow mode, GetWindowText() is used 139 | ; to retrieve the text of each control. 140 | WinGet controls, ControlListHwnd 141 | static WINDOW_TEXT_SIZE := 32767 ; Defined in AutoHotkey source. 142 | VarSetCapacity(buf, WINDOW_TEXT_SIZE * (A_IsUnicode ? 2 : 1)) 143 | text := "" 144 | Loop Parse, controls, `n 145 | { 146 | if !detect_hidden && !DllCall("IsWindowVisible", "ptr", A_LoopField) 147 | continue 148 | if !DllCall("GetWindowText", "ptr", A_LoopField, "str", buf, "int", WINDOW_TEXT_SIZE) 149 | continue 150 | text .= buf "`r`n" 151 | } 152 | return text 153 | } 154 | 155 | GetClientSize(hWnd, ByRef w := "", ByRef h := "") 156 | { 157 | VarSetCapacity(rect, 16) 158 | DllCall("GetClientRect", "ptr", hWnd, "ptr", &rect) 159 | w := NumGet(rect, 8, "int") 160 | h := NumGet(rect, 12, "int") 161 | } 162 | 163 | WinToClient(hWnd, ByRef x, ByRef y) 164 | { 165 | WinGetPos wX, wY,,, ahk_id %hWnd% 166 | x += wX, y += wY 167 | VarSetCapacity(pt, 8), NumPut(y, NumPut(x, pt, "int"), "int") 168 | if !DllCall("ScreenToClient", "ptr", hWnd, "ptr", &pt) 169 | return false 170 | x := NumGet(pt, 0, "int"), y := NumGet(pt, 4, "int") 171 | return true 172 | } 173 | 174 | textMangle(x) 175 | { 176 | if pos := InStr(x, "`n") 177 | x := SubStr(x, 1, pos-1), elli := true 178 | if StrLen(x) > 40 179 | { 180 | StringLeft, x, x, 40 181 | elli := true 182 | } 183 | if elli 184 | x .= " (...)" 185 | return x 186 | } 187 | 188 | ~*Ctrl:: 189 | ~*Shift:: 190 | SetTimer, Update, Off 191 | GuiControl, %hGui%:, Ctrl_Freeze, % txtFrozen 192 | return 193 | 194 | ~*Ctrl up:: 195 | ~*Shift up:: 196 | SetTimer, Update, On 197 | return 198 | -------------------------------------------------------------------------------- /pyzdde/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: config.py 4 | # Purpose: Configuration module 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 os import path as _path 10 | import sys as _sys 11 | 12 | try: # Python 3.x 13 | from configparser import SafeConfigParser 14 | except ImportError: # Python 2.x 15 | from ConfigParser import SafeConfigParser 16 | 17 | # Helper functions for chaning settings.ini file 18 | def setTextEncoding(txt_encoding=0): 19 | """sets the text encoding to match the TXT encoding in Zemax 20 | 21 | Parameters 22 | ---------- 23 | txt_encoding : integer 24 | 0 = ASCII; 1 = UNICODE 25 | 26 | Returns 27 | ------- 28 | status : bool 29 | ``True`` if success; ``False`` if fail 30 | """ 31 | global _global_use_unicode_text 32 | status = False 33 | if txt_encoding: # unicode 34 | success = changeEncodingConfiguration(encoding_type=0, encoding=1) 35 | if success: 36 | new_encoding = getEncodingConfiguration(encoding_type=0) 37 | if new_encoding == 'unicode': 38 | _global_use_unicode_text = True 39 | status = True 40 | else: # ascii 41 | success = changeEncodingConfiguration(encoding_type=0, encoding=0) 42 | if success: 43 | new_encoding = getEncodingConfiguration(encoding_type=0) 44 | if new_encoding == 'ascii': 45 | _global_use_unicode_text = False 46 | status = True 47 | return status 48 | 49 | def getTextEncoding(): 50 | """returns the current text encoding set in PyZDDE 51 | This is an internal helper function 52 | """ 53 | return getEncodingConfiguration(encoding_type=0) 54 | 55 | def getSettingsFileFullName(): 56 | """returns the full path of the settings.ini file""" 57 | settings_file_name = 'settings.ini' 58 | dirpath = _path.dirname(_path.realpath(__file__)) 59 | filepath = _path.join(dirpath, settings_file_name) 60 | return filepath 61 | 62 | def getEncodingConfiguration(encoding_type=0): 63 | """get the encoding in the configuration file 64 | 65 | Parameters 66 | ---------- 67 | encoding_type : integer 68 | 0 = Text encoding; 1 = File encoding 69 | 70 | Returns 71 | ------- 72 | encoding : string 73 | 'ascii' or 'unicode' 74 | """ 75 | parser = SafeConfigParser() 76 | parser.read(getSettingsFileFullName()) 77 | if encoding_type: # file encoding 78 | pass 79 | else: # text encoding 80 | encoding = parser.get('zemax_text_encoding', 'encoding') 81 | return encoding 82 | 83 | def changeEncodingConfiguration(encoding_type=0, encoding=0): 84 | """change the encoding in the configuration file (settings.ini) 85 | 86 | Parameters 87 | ---------- 88 | encoding_type : integer 89 | 0 = Text encoding; 1 = File encoding 90 | encoding : integer 91 | 0 = ascii; 1 = unicode 92 | 93 | Returns 94 | ------- 95 | status : bool 96 | True = success; False = failed 97 | """ 98 | parser = SafeConfigParser() 99 | parser.read(getSettingsFileFullName()) 100 | status = True 101 | if encoding_type: # file encoding 102 | pass 103 | else: # text encoding 104 | if encoding: # unicode 105 | parser.set(section='zemax_text_encoding', 106 | option='encoding', 107 | value='unicode') 108 | else: # ascii 109 | parser.set(section='zemax_text_encoding', 110 | option='encoding', 111 | value='ascii') 112 | try: 113 | cfgfile = open(getSettingsFileFullName(), 'w') 114 | parser.write(cfgfile) 115 | cfgfile.close() 116 | except: 117 | status = False 118 | return status 119 | 120 | def getImageMagickSettings(): 121 | """return the use-flag and imageMagick installation directory 122 | settings 123 | 124 | Parameters 125 | ---------- 126 | None 127 | 128 | Returns 129 | ------- 130 | use_flag : bool 131 | if ``True``, then PyZDDE uses the installed version of ImageMagick 132 | software. If ``False``, then the version of ImageMagick that 133 | comes with PyZDDE will be used. 134 | imageMagick_dir : string 135 | ImageMagick installation directory. 136 | """ 137 | parser = SafeConfigParser() 138 | parser.read(getSettingsFileFullName()) 139 | image_magick_settings = parser.items('imageMagick_config') 140 | use_flag_index, dir_index, value_index = 0, 1, 1 141 | use_flag = eval(image_magick_settings[use_flag_index][value_index]) 142 | imageMagick_dir = image_magick_settings[dir_index][value_index] 143 | return use_flag, imageMagick_dir 144 | 145 | 146 | def setImageMagickSettings(use_installed_ImageMagick, 147 | imageMagick_dir=None): 148 | """set the use-flag and imageMagick installation directory settings 149 | 150 | Parameters 151 | ---------- 152 | use_installed_ImageMagick : bool 153 | boolean flag to indicate whether to use installed version 154 | of ImageMagick (``True``) or not (``False``) 155 | imageMagick_dir : string, optional 156 | full path to the installation directory. For example: 157 | ``C:\\Program Files\\ImageMagick-6.8.9-Q8`` 158 | 159 | Returns 160 | ------- 161 | status : bool 162 | ``True`` = success; ``False`` = fail 163 | """ 164 | status = True 165 | parser = SafeConfigParser() 166 | parser.read(getSettingsFileFullName()) 167 | parser.set(section='imageMagick_config', 168 | option='useInstalled', 169 | value=str(use_installed_ImageMagick)) 170 | if imageMagick_dir: 171 | parser.set(section='imageMagick_config', 172 | option='imgMagickIinstallDir', 173 | value=imageMagick_dir) 174 | try: 175 | cfgfile = open(getSettingsFileFullName(), 'w') 176 | parser.write(cfgfile) 177 | cfgfile.close() 178 | except: 179 | status = False 180 | return status 181 | 182 | 183 | # ------------------------------------------------------- 184 | # Get Zemax text encoding variable from settings .ini file 185 | text_encoding = getEncodingConfiguration(encoding_type=0) 186 | if text_encoding == 'unicode': 187 | _global_use_unicode_text = True 188 | #print('config.py:: text setting is UNICODE') 189 | else: 190 | _global_use_unicode_text = False 191 | #print('config.py:: text setting is ASCII') 192 | 193 | # Python version variable 194 | _global_pyver3 = None 195 | # Check Python version and set the global version variable 196 | if _sys.version_info[0] < 3: 197 | _global_pyver3 = False 198 | else: 199 | _global_pyver3 = True -------------------------------------------------------------------------------- /pyzdde/utils/pyzddeutils.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # Name: pyzddeutils.py 3 | # Purpose: Utility functions for use with and within (mostly) PyZDDE 4 | # 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 | '''utility functions for use within (mostly) and with the PyZDDE library 10 | ''' 11 | from __future__ import print_function 12 | 13 | # Use IPython's display module to print objects if module is executing in an IPython 14 | # environment, else use standard print() 15 | 16 | try: 17 | import IPython.core as ipcore 18 | except ImportError: # No IPython 19 | IPLoad = False 20 | _print_mod = print 21 | else: # 22 | try: 23 | ipyEnv = ipcore.getipython.get_ipython() 24 | except: # older version of IPython (ver < 4.0) 25 | try: 26 | get_ipython() 27 | except: 28 | _print_mod = print 29 | else: 30 | import IPython.core.display as display 31 | _print_mod = display.display 32 | else: 33 | if ipyEnv: 34 | _print_mod = ipcore.display.display # in modern IPython environment 35 | else: 36 | _print_mod = print 37 | 38 | try: 39 | import matplotlib.pyplot as plt 40 | except ImportError: 41 | MplPltLoad = False 42 | else: 43 | MplPltLoad = True 44 | 45 | try: 46 | import numpy as np 47 | except ImportError: 48 | NpyLoad = False 49 | else: 50 | NpyLoad = True 51 | 52 | 53 | class _prettifyCodeDesc(object): 54 | """Class to enable colorized Code-Description string output in IPython environment 55 | 56 | Usage: ``_print_mod(_prettifyCodeDesc("CODE", "Description")`` 57 | """ 58 | def __init__(self, code, desc, color0='blue', color1='magenta'): 59 | self.code = code 60 | self.desc = desc 61 | self.color0 = color0 62 | self.color1 = color1 63 | def _repr_html_(self): 64 | return ("[{code}" 65 | "] {desc}" 66 | .format(c0=self.color0,c1=self.color1,code=self.code,desc=self.desc)) 67 | def __repr__(self): 68 | return "[%s] %s" % (self.code, self.desc) 69 | 70 | class _prettifyText(object): 71 | """Class to enable colorized string output in IPython environment 72 | 73 | Usage: ``_print_mod(_prettifyText("text0", "text1" [, "text2", "color0", "color1", "color2"])`` 74 | """ 75 | def __init__(self,text0,text1,text2='',color0='red',color1='green',color2='red'): 76 | self.text0 = text0 77 | self.text1 = text1 78 | self.text2 = text2 79 | self.color0 = color0 80 | self.color1 = color1 81 | self.color2 = color2 82 | def _repr_html_(self): 83 | return ("{text0}" 84 | "{text1}" 85 | "{text2}" 86 | .format(c0=self.color0,c1=self.color1,c2=self.color2, 87 | text0=self.text0,text1=self.text1,text2=self.text2)) 88 | def __repr__(self): 89 | return "%s%s%s" % (self.text0, self.text1, self.text2) 90 | 91 | class _boldifyText(object): 92 | """Class to enable colorized and bold string output in IPython environment 93 | 94 | Usage: ``_print_mod(_boldifyText("text0", "text1" [, "text2", "color0", "color1", "color2"])`` 95 | """ 96 | def __init__(self,text0,text1,text2='',color0='red',color1='green',color2='red'): 97 | self.text0 = text0 98 | self.text1 = text1 99 | self.text2 = text2 100 | self.color0 = color0 101 | self.color1 = color1 102 | self.color2 = color2 103 | def _repr_html_(self): 104 | return ("{text0}" 105 | "{text1}" 106 | "{text2}" 107 | .format(c0=self.color0,c1=self.color1,c2=self.color2, 108 | text0=self.text0,text1=self.text1,text2=self.text2)) 109 | def __repr__(self): 110 | return "%s%s%s" % (self.text0, self.text1, self.text2) 111 | 112 | def cropImgBorders(pixarr, left, right, top, bottom): 113 | """crops boder pixels from an image array 114 | 115 | Parameters 116 | ---------- 117 | pixarr : ndarray 118 | The image pixel array of ndim >=3 119 | left : integer 120 | number of pixels to crop from left 121 | right : integer 122 | number of pixels to crop from right 123 | top : integer 124 | number of pixels to crop from top 125 | bottom : integer 126 | number of pixels to crop from bottom 127 | 128 | Returns 129 | ------- 130 | newarr : ndarray 131 | The cropped image pixel array 132 | 133 | Note 134 | ---- 135 | Requires Numpy 136 | """ 137 | if NpyLoad: 138 | rows, cols = pixarr.shape[0:2] 139 | newarr = np.zeros((rows-top-bottom, cols - left - right, pixarr.shape[2]), dtype=pixarr.dtype) 140 | for i in range(pixarr.shape[2]): 141 | newarr[:,:,i] = pixarr[top:rows-bottom, left:cols-right, i] 142 | return newarr 143 | else: 144 | print("The function couldn't be executed. Requires Numpy") 145 | 146 | def imshow(pixarr, cropBorderPixels=(0, 0, 0, 0), figsize=None, title=None, fig=None, faxes=None): 147 | """Convenience function to render the screenshot 148 | 149 | Parameters 150 | ---------- 151 | pixarr : ndarray 152 | The image pixel array 153 | cropBorderPixels : 4-tuple of integer elements 154 | (left, right, top, bottom) where `left`, `right`, `top` and `bottom` indicates 155 | the number of pixels to crop from left, right, top, and bottom of the image 156 | figsize : 2-tuple 157 | Figure size in inches (default=None) 158 | title : string 159 | figure title (default=None) 160 | fig : matplotlib figure object (optional) 161 | If a figure object is passed, the given figure will be used for plotting. 162 | faxes : matplotlib figure axis (optional, however recommended if `fig` is passed) 163 | If a figure object is passed, it is recommended to also pass an axes object. Also, 164 | if an `faxes` is passed, the `fig` must also be given. 165 | 166 | Returns 167 | ------- 168 | None 169 | 170 | Note 171 | ---- 172 | If `fig` is provided, then the function will not call `plt.show`. Please use 173 | `plt.show` as required (after the function returns) 174 | """ 175 | if MplPltLoad and NpyLoad: 176 | if fig and faxes: 177 | figure = fig 178 | ax = faxes 179 | elif fig and not faxes: 180 | figure = fig 181 | ax = figure.add_subplot(111) 182 | elif not fig and faxes: 183 | print("Please pass a valid figure object along the given axes.") 184 | return 185 | else: 186 | figure = plt.figure(figsize=figsize) 187 | ax = figure.add_subplot(111) 188 | left, right, top, bottom = cropBorderPixels 189 | narr = cropImgBorders(pixarr, left, right, top, bottom) 190 | ax.imshow(narr, interpolation='spline36') 191 | if title: 192 | ax.set_title(title,fontsize=13) 193 | # remove chart-junk 194 | # remove ticks and splines 195 | ax.get_xaxis().set_ticks([]) 196 | ax.get_yaxis().set_ticks([]) 197 | ax.spines['right'].set_color('none') 198 | ax.spines['left'].set_color('none') 199 | ax.spines['top'].set_color('none') 200 | ax.spines['bottom'].set_color('none') 201 | if not fig and not faxes: 202 | plt.show() 203 | else: 204 | print("The function couldn't be executed. Requires Numpy and/or Matplotlib") 205 | 206 | if __name__ == '__main__': 207 | pass -------------------------------------------------------------------------------- /settings.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 379 10 | 396 11 | 12 | 13 | 14 | Settings 15 | 16 | 17 | 18 | 19 | 20 20 | 40 21 | 191 22 | 23 23 | 24 | 25 | 26 | Choose Working Directory 27 | 28 | 29 | 30 | 31 | 32 | 20 33 | 100 34 | 191 35 | 23 36 | 37 | 38 | 39 | Choose ZEMAX Path 40 | 41 | 42 | 43 | 44 | 45 | 20 46 | 70 47 | 311 48 | 16 49 | 50 | 51 | 52 | Working Dir: 53 | 54 | 55 | 56 | 57 | 58 | 20 59 | 130 60 | 311 61 | 16 62 | 63 | 64 | 65 | ZEMAX Path: 66 | 67 | 68 | 69 | 70 | 71 | 20 72 | 190 73 | 311 74 | 16 75 | 76 | 77 | 78 | Inventor Path: 79 | 80 | 81 | 82 | 83 | 84 | 20 85 | 160 86 | 191 87 | 23 88 | 89 | 90 | 91 | Choose Inventor Path 92 | 93 | 94 | 95 | 96 | 97 | 20 98 | 220 99 | 281 100 | 151 101 | 102 | 103 | 104 | Rayfiles 105 | 106 | 107 | 108 | false 109 | 110 | 111 | 112 | 60 113 | 20 114 | 113 115 | 20 116 | 117 | 118 | 119 | false 120 | 121 | 122 | false 123 | 124 | 125 | 126 | 127 | 128 | 190 129 | 20 130 | 70 131 | 17 132 | 133 | 134 | 135 | Enable 136 | 137 | 138 | 139 | 140 | 141 | 160 142 | 120 143 | 101 144 | 23 145 | 146 | 147 | 148 | Save 149 | 150 | 151 | 152 | 153 | 154 | 190 155 | 40 156 | 70 157 | 17 158 | 159 | 160 | 161 | Enable 162 | 163 | 164 | 165 | 166 | false 167 | 168 | 169 | 170 | 60 171 | 40 172 | 113 173 | 20 174 | 175 | 176 | 177 | false 178 | 179 | 180 | false 181 | 182 | 183 | 184 | 185 | 186 | 190 187 | 60 188 | 70 189 | 17 190 | 191 | 192 | 193 | Enable 194 | 195 | 196 | 197 | 198 | false 199 | 200 | 201 | 202 | 60 203 | 60 204 | 113 205 | 20 206 | 207 | 208 | 209 | false 210 | 211 | 212 | false 213 | 214 | 215 | 216 | 217 | false 218 | 219 | 220 | 221 | 60 222 | 80 223 | 113 224 | 20 225 | 226 | 227 | 228 | false 229 | 230 | 231 | false 232 | 233 | 234 | 235 | 236 | 237 | 190 238 | 80 239 | 70 240 | 17 241 | 242 | 243 | 244 | Enable 245 | 246 | 247 | 248 | 249 | 250 | 20 251 | 20 252 | 31 253 | 16 254 | 255 | 256 | 257 | Field1: 258 | 259 | 260 | 261 | 262 | 263 | 20 264 | 40 265 | 31 266 | 16 267 | 268 | 269 | 270 | Field2: 271 | 272 | 273 | 274 | 275 | 276 | 20 277 | 60 278 | 31 279 | 16 280 | 281 | 282 | 283 | Field3: 284 | 285 | 286 | 287 | 288 | 289 | 20 290 | 80 291 | 31 292 | 16 293 | 294 | 295 | 296 | Field4: 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | -------------------------------------------------------------------------------- /gui.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1148 10 | 555 11 | 12 | 13 | 14 | Opti² 15 | 16 | 17 | 18 | 19 | 480 20 | 10 21 | 571 22 | 16 23 | 24 | 25 | 26 | Working Dir: 27 | 28 | 29 | 30 | 31 | 32 | 190 33 | 20 34 | 260 35 | 401 36 | 37 | 38 | 39 | Zemax Functions 40 | 41 | 42 | 43 | 1 44 | 45 | 46 | 47 | 48 | Open Zemax 49 | 50 | 51 | 52 | 53 | 54 | 55 | Save Current Model 56 | 57 | 58 | 59 | 60 | 61 | 62 | Load SC-Model 63 | 64 | 65 | 66 | 67 | 68 | 69 | Save and convert to NSC-Model 70 | 71 | 72 | false 73 | 74 | 75 | false 76 | 77 | 78 | false 79 | 80 | 81 | 82 | 83 | 84 | 85 | Load Live NSC-Model 86 | 87 | 88 | 89 | 90 | 91 | 92 | Save and convert to CAD-Model 93 | 94 | 95 | 96 | 97 | 98 | 99 | Restore Saved Model 100 | 101 | 102 | 103 | 104 | 105 | 106 | Qt::Horizontal 107 | 108 | 109 | 110 | 40 111 | 20 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | Trace 120 | 121 | 122 | 123 | 124 | 125 | 126 | Save Detector file 127 | 128 | 129 | 130 | 131 | 132 | 133 | Delete Selected Model 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 470 143 | 60 144 | 651 145 | 401 146 | 147 | 148 | 149 | 150 | 0 151 | 0 152 | 153 | 154 | 155 | 156 | 0 157 | 0 158 | 159 | 160 | 161 | 162 | 163 | 164 | false 165 | 166 | 167 | QListView::Adjust 168 | 169 | 170 | true 171 | 172 | 173 | true 174 | 175 | 176 | 177 | 178 | 179 | 10 180 | 20 181 | 171 182 | 211 183 | 184 | 185 | 186 | General Settings 187 | 188 | 189 | 190 | 191 | 192 | Open Settings 193 | 194 | 195 | 196 | 197 | 198 | 199 | Create Subfolders 200 | 201 | 202 | 203 | 204 | 205 | 206 | Edit comment 207 | 208 | 209 | 210 | 211 | 212 | 213 | Refresh 214 | 215 | 216 | 217 | 218 | 219 | 220 | Help 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 470 230 | 35 231 | 71 232 | 21 233 | 234 | 235 | 236 | Version 237 | 238 | 239 | 240 | 241 | 242 | 550 243 | 35 244 | 51 245 | 21 246 | 247 | 248 | 249 | Date 250 | 251 | 252 | 253 | 254 | 255 | 740 256 | 35 257 | 91 258 | 21 259 | 260 | 261 | 262 | Comment 263 | 264 | 265 | 266 | 267 | 268 | 10 269 | 340 270 | 171 271 | 101 272 | 273 | 274 | 275 | Optimization Settings 276 | 277 | 278 | 279 | 280 | 70 281 | 30 282 | 62 283 | 22 284 | 285 | 286 | 287 | 3 288 | 289 | 290 | 100000000000000009190283508143378238084034459715684532224.000000000000000 291 | 292 | 293 | 294 | 295 | 296 | 70 297 | 70 298 | 62 299 | 22 300 | 301 | 302 | 303 | 3 304 | 305 | 306 | 1000000000000000078291540404596243842305360299886116864.000000000000000 307 | 308 | 309 | 310 | 311 | 312 | 10 313 | 70 314 | 71 315 | 21 316 | 317 | 318 | 319 | Target: 320 | 321 | 322 | 323 | 324 | 325 | 10 326 | 30 327 | 71 328 | 21 329 | 330 | 331 | 332 | Weight 333 | 334 | 335 | 336 | 337 | 338 | 339 | 20 340 | 260 341 | 161 342 | 16 343 | 344 | 345 | 346 | Power last run: 347 | 348 | 349 | 350 | 351 | 352 | 20 353 | 280 354 | 141 355 | 25 356 | 357 | 358 | 359 | Optimization 360 | 361 | 362 | 363 | 364 | 365 | 930 366 | 470 367 | 181 368 | 51 369 | 370 | 371 | 372 | 373 | 374 | 375 | IPeG_Logo_kurz.jpg 376 | 377 | 378 | true 379 | 380 | 381 | 382 | 383 | 384 | 385 | -------------------------------------------------------------------------------- /pyzdde/systems.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: systems.py 4 | # Purpose: Simple optical systems for quick setup with PyZDDE. 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 | """simple optical systems for quick setup with PyZDDE & Zemax. The docstring 10 | examples assume that PyZDDe is imported as ``import pyzdde.zdde as pyz``, 11 | a PyZDDE communication object is then created as ``ln = pyz.createLink()`` 12 | or ``ln = pyz.PyZDDE(); ln.zDDEInit()`` and ``systems`` (this module) is 13 | imported as ``import pyzdde.systems as optsys`` 14 | """ 15 | 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | def zMakeIdealThinLens(ddeLn, fl=50, fn=5, stop_pos=0, stop_shift=0, opd_mode=1, 20 | zmx_mode=0): 21 | """Creates an ideal thin lens of the given specification consisting of 22 | a STOP and a PARAXIAL surface in the zemax server. 23 | 24 | Parameters 25 | ---------- 26 | ddeLn : object 27 | pyzdde object 28 | fl : float, optional 29 | focal length (measured in air of unity index) in lens units 30 | fn : float, optional 31 | f-number (image space f/#) 32 | stop_pos : integer (0/1), optional 33 | use 0 to place STOP before (to the left of) the paraxial 34 | surface, 1 to place STOP after (to the right) 35 | stop_shift : integer, optional 36 | axial distance between STOP and paraxial surface 37 | opd_mode : integer (0, 1, 2 or 3), optional 38 | the OPD mode, which indicates how Zemax should calculate the 39 | optical path difference for rays refracted by the paraxial lens. 40 | 41 | * ``opd_mode=0`` is fast and accurate if the aberrations are not 42 | severe. Zemax uses parabasal ray tracing in this mode. 43 | * ``opd_mode=1`` is the most accurate. 44 | * ``opd_mode=2`` assumes that the lens is used at infinite 45 | conjugates. 46 | * ``opd_mode=3`` is similar to ``opd_mode=0``, except that zemax 47 | traces paraxial rays instead of paraxial rays. 48 | 49 | zmx_mode : integer (0, 1, or 2), optional 50 | zemax mode. 0 for sequential, 1 for hybrid, 2 for mixed. Currently 51 | ignored. 52 | 53 | Returns 54 | ------- 55 | None 56 | 57 | Examples 58 | -------- 59 | >>> import pyzdde.zdde as pyz 60 | >>> import pyzdde.systems as optsys 61 | >>> ln = pyz.createLink() 62 | >>> optsys.zMakeIdealThinLens(ln) 63 | >>> optsys.zMakeIdealThinLens(ln, fl=100, fn=5, opd_mode=0) 64 | >>> optsys.zMakeIdealThinLens(ln, stop_shift=5) 65 | >>> optsys.zMakeIdealThinLens(ln, stop_pos=1, stop_shift=5) 66 | 67 | Notes 68 | ----- 69 | 1. For more information see "Paraxial" under "Sequential surface type 70 | definitions" in the Zemax manual. 71 | 2. Use ``ln.zPushLens(1)`` update lens into the LDE 72 | """ 73 | if stop_pos < 0 or stop_pos > 1: 74 | raise ValueError("Expecting stop_pos to be either 0 or 1") 75 | epd = fl/fn 76 | stop_surf = 2 if stop_pos else 1 77 | para_surf = 1 if stop_pos else 2 78 | ddeLn.zNewLens() 79 | ddeLn.zInsertSurface(para_surf) 80 | ddeLn.zSetSystemAper(aType=0, stopSurf=stop_surf, aperVal=epd) 81 | if stop_pos: 82 | ddeLn.zSetSurfaceData(para_surf, code=3, value=stop_shift) 83 | else: 84 | ddeLn.zSetSurfaceData(stop_surf, code=3, value=stop_shift) 85 | ddeLn.zSetSurfaceData(para_surf, code=0, value='PARAXIAL') 86 | ddeLn.zSetSurfaceParameter(para_surf, param=1, value=fl) # focallength 87 | ddeLn.zSetSurfaceParameter(para_surf, param=2, value=opd_mode) 88 | surf_beforeIMA, thickness_code = 2, 1 89 | solve_type, height, pupil_zone = 2, 0, 0 # Marginal ray height 90 | ddeLn.zSetSolve(surf_beforeIMA, thickness_code, solve_type, height, pupil_zone) 91 | 92 | def zMakeIdealCollimator(ddeLn, fl=50, fn=5, ima_dist=10, opd_mode=1, zmx_mode=0): 93 | """Creates a collimator using an ideal thin lens of the given 94 | specification. 95 | 96 | The model consists of just 3 surfaces in the LDE -- OBJ, STOP 97 | (paraxial surface) and IMA plane. 98 | 99 | Parameters 100 | ---------- 101 | ddeLn : object 102 | pyzdde object 103 | fl : float, optional 104 | focal length (measured in air of unity index) in lens units 105 | fn : float, optional 106 | f-number (image space f/#) 107 | ima_dist : integer, optional 108 | axial distance the paraxial surface and IMA (observation plane) 109 | opd_mode : integer (0, 1, 2 or 3), optional 110 | the OPD mode, which indicates how Zemax should calculate the 111 | optical path difference for rays refracted by the paraxial lens. 112 | 113 | * ``opd_mode=0`` is fast and accurate if the aberrations are not 114 | severe. Zemax uses parabasal ray tracing in this mode. 115 | * ``opd_mode=1`` is the most accurate. 116 | * ``opd_mode=2`` assumes that the lens is used at infinite 117 | conjugates. 118 | * ``opd_mode=3`` is similar to ``opd_mode=0``, except that zemax 119 | traces paraxial rays instead of paraxial rays. 120 | 121 | zmx_mode : integer (0, 1, or 2), optional 122 | zemax mode. 0 for sequential, 1 for hybrid, 2 for mixed. Currently 123 | ignored. 124 | 125 | Returns 126 | ------- 127 | None 128 | 129 | Examples 130 | -------- 131 | >>> optsys.zMakeIdealCollimator(ln) 132 | >>> optsys.zMakeIdealCollimator(ln, fl=100, fn=5, opd_mode=0) 133 | >>> optsys.zMakeIdealThinLens(ln, stop_shift=5) 134 | >>> optsys.zMakeIdealThinLens(ln, stop_pos=1, stop_shift=5) 135 | 136 | Notes 137 | ----- 138 | 1. For more information see "Paraxial" under "Sequential surface type 139 | definitions" in the Zemax manual. 140 | 2. Use ``ln.zPushLens(1)`` update lens into the LDE 141 | """ 142 | epd = fl/fn 143 | ddeLn.zNewLens() 144 | ddeLn.zSetSystemAper(aType=0, stopSurf=1, aperVal=epd) 145 | ddeLn.zSetSystemProperty(code=18, value1=1) # Afocal Image Space 146 | ddeLn.zSetSurfaceData(surfNum=0, code=3, value=fl) 147 | ddeLn.zSetSurfaceData(surfNum=1, code=0, value='PARAXIAL') 148 | ddeLn.zSetSurfaceData(surfNum=1, code=3, value=ima_dist) 149 | ddeLn.zSetSurfaceParameter(surfNum=1, param=1, value=fl) # focallength 150 | ddeLn.zSetSurfaceParameter(surfNum=1, param=2, value=opd_mode) 151 | 152 | def zMakeBeamExpander(ddeLn, inDia=5.0, outDia=10.0, expGlass='N-BK7', expThick=10.0, 153 | colGlass='N-BK7', colThick=10.0, preExpThick=5.0, preColThick=200.0, 154 | preImgThick=10.0, insertAfter=None, insertOperandRow=1, epd=None, 155 | setSysAper=True, afocal=True): 156 | """Creates a basic beam expander system consisting of a expander and 157 | a collimator lens (totally 5 surfaces excluding OBJ and IMA) 158 | 159 | Parameters 160 | ---------- 161 | inDia : float, optional 162 | input beam diameter in lens units (Default = 5.0 mm) 163 | outDia : float 164 | output beam diameter in lens units (Default = 10.0 mm) 165 | expGlass : string 166 | expander glass type (Default = 'N-BK7') 167 | expThick : float, optional 168 | thickness of the expander surface (Default = 10.0 mm) 169 | colGlass : string, optional 170 | collimator glass type (Default = 'N-BK7') 171 | preExpThick : float, optional 172 | thickness of surface before the first collimator surface, in mm. 173 | (Default = 5.0 mm) 174 | preColThick : float, optional 175 | thickness of the surface before collimator lens (between expander 176 | and collimator, Default = 200.0 mm) 177 | preImgThick : float, optional 178 | thickness of the surface after collimator (Default = 10.0 mm) 179 | insertAfter : integer, optional 180 | surface number after which to insert beam expander system 181 | insertOperandRow : integer, optional 182 | row number in MFE to insert the "REAY" operand for output beam 183 | epd : float, optional 184 | entrance pupil diameter, if desirable to set a value different 185 | from ``inDia``. If not provided, the ``EPD`` value of the system 186 | is set to ``inDia`` value. 187 | setSysAper : bool, optional 188 | by default the system aperture is set in the fucntion, such that 189 | the system aperture is "Entrance pupil diameter", and the value 190 | of the EPD is dependent on ``inDia`` and ``epd``. 191 | afocal : bool, optional 192 | by default the system is set to be image space afocal system. If 193 | False, then this setting is skipped. 194 | 195 | Returns 196 | ------- 197 | None 198 | 199 | Examples 200 | -------- 201 | >>> import pyzdde.zdde as pyz 202 | >>> ln = pyz.createLink() 203 | >>> optsys.zMakeBeamExpander(ln) 204 | 205 | Notes 206 | ----- 207 | The system created by this function is not optimized. It only sets up 208 | the system and places variable solves on the radii of the appropriate 209 | surfaces. Please set up the MFE as described in [HTA]_ and optimize. 210 | 211 | References 212 | ---------- 213 | The beam expander created by this function is similar to the one shown 214 | in the Zemax knowledgebase article [HTA]_ 215 | 216 | 217 | .. [HTA] How to design Afocal Systems. Link: http://kb-en.radiantzemax.com/Knowledgebase/How-to-Design-Afocal-Systems 218 | """ 219 | epd = inDia if epd is None else epd 220 | # the stop surface is the first surface of the Expander 221 | stop_surf = ddeLn.zGetSystemAper().stopSurf 222 | if insertAfter is None: 223 | ddeLn.zNewLens() 224 | if setSysAper: 225 | ddeLn.zSetSystemAper(aType=0, stopSurf=stop_surf, aperVal=epd) 226 | if afocal: 227 | ddeLn.zSetSystemProperty(code=18, value1=1) # Afocal Image Space 228 | # instert surfaces 229 | if insertAfter: 230 | in_beam_surf = insertAfter + 1 231 | ddeLn.zInsertSurface(in_beam_surf) 232 | else: 233 | in_beam_surf = stop_surf 234 | ddeLn.zSetSurfaceData(in_beam_surf, code=1, value="input beam") 235 | ddeLn.zSetSurfaceData(in_beam_surf, code=3, value=preExpThick) 236 | exp_ft_surf, exp_bk_surf = in_beam_surf + 1, in_beam_surf + 2 237 | col_ft_surf, col_bk_surf = in_beam_surf + 3, in_beam_surf + 4 238 | ddeLn.zInsertSurface(exp_ft_surf) 239 | ddeLn.zSetSurfaceData(exp_ft_surf, code=1, value="expander") 240 | ddeLn.zSetSurfaceData(exp_ft_surf, code=3, value=expThick) 241 | ddeLn.zSetSurfaceData(exp_ft_surf, code=4, value=expGlass) 242 | ddeLn.zInsertSurface(exp_bk_surf) 243 | ddeLn.zSetSurfaceData(exp_bk_surf, code=1, value=" ") 244 | ddeLn.zSetSurfaceData(exp_bk_surf, code=3, value=preColThick) 245 | ddeLn.zInsertSurface(col_ft_surf) 246 | ddeLn.zSetSurfaceData(col_ft_surf, code=1, value="collimator") 247 | ddeLn.zSetSurfaceData(col_ft_surf, code=3, value=colThick) 248 | ddeLn.zSetSurfaceData(col_ft_surf, code=4, value=colGlass) 249 | ddeLn.zInsertSurface(col_bk_surf) 250 | ddeLn.zSetSurfaceData(col_bk_surf, code=1, value=" ") 251 | ddeLn.zSetSurfaceData(col_bk_surf, code=3, value=preImgThick) 252 | # Set variable solves on the Radii of the 253 | for surf in [exp_ft_surf, exp_bk_surf, col_ft_surf, col_bk_surf]: 254 | ddeLn.zSetSolve(surf, 0, 1) 255 | # Set MFE operand on for the output diameter of the beam 256 | pWave = ddeLn.zGetPrimaryWave() 257 | ddeLn.zInsertMFO(insertOperandRow) 258 | ddeLn.zSetOperandRow(insertOperandRow, 'REAY', int1=col_bk_surf+1, 259 | int2=pWave, data4=1.0, tgt=outDia/2.0, wgt=1.0) 260 | -------------------------------------------------------------------------------- /pyzdde/misc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: misc.py 4 | # Purpose: Miscellaneous utility functions. 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 | '''miscellaneous utility functions. 10 | ''' 11 | from __future__ import print_function, division 12 | import os as _os 13 | import zdde as _pyz 14 | import collections as _co 15 | 16 | import pyzdde.config as _config 17 | _global_pyver3 = _config._global_pyver3 18 | 19 | if _global_pyver3: 20 | xrange = range 21 | 22 | try: 23 | import numpy as _np 24 | except ImportError: 25 | _global_np = False 26 | else: 27 | _global_np = True 28 | 29 | 30 | 31 | 32 | def _draw_plane(ln, space='img', dist=0, surfName=None, semiDia=None): 33 | """function to draw planes at the points specified by `dist` in the 34 | space specified by `space` 35 | 36 | Parameters 37 | ---------- 38 | ln : pyzdde object 39 | active link object 40 | space : string (`img` or `obj`), optional 41 | image space or object space in which the plane is specified. 'img' for 42 | image space, 'obj' for object space. This info is required because 43 | Zemax returns distances that are measured w.r.t. surface 1 (@LDE) in 44 | object space, and w.r.t. IMG in image space. See the Assumptions. 45 | dist : float, optional 46 | distance along the optical axis of the plane from surface 2 (@LDE) if 47 | `space` is `obj` else from the IMG surface. This assumes that surface 1 48 | is a dummy surface 49 | surfName : string, optional 50 | name to identify the surf in the LDE, added to the comments column 51 | semiDia : real, optional 52 | semi-diameter of the surface to set 53 | 54 | Returns 55 | ------- 56 | None 57 | 58 | Assumptions (important to read) 59 | ------------------------------- 60 | The function assumes (for the purpose of this study) that surface 1 @ LDE is 61 | a dummy surface at certain distance preceding the first actual lens surface. 62 | This enables the rays entering the lens to be visible in the Zemax layout 63 | plots even if the object is at infinity. So the function inserts the planes 64 | (and their associated dummy surfaces) beginning at surface 2. 65 | """ 66 | numSurf = ln.zGetNumSurf() 67 | inSurfPos = numSurf if space=='img' else 2 # assuming that the first surface will be a dummy surface 68 | ln.zInsertDummySurface(surfNum=inSurfPos, thick=dist, semidia=0, comment='dummy') 69 | ln.zInsertSurface(inSurfPos+1) 70 | ln.zSetSurfaceData(inSurfPos+1, ln.SDAT_COMMENT, surfName) 71 | if semiDia: 72 | ln.zSetSemiDiameter(surfNum=inSurfPos+1, value=semiDia) 73 | thickSolve, pickupSolve = 1, 5 74 | frmSurf, scale, offset, col = inSurfPos, -1, 0, 0 75 | ln.zSetSolve(inSurfPos+1, thickSolve, pickupSolve, frmSurf, scale, offset, col) 76 | 77 | def gaussian_lens_formula(u=None, v=None, f=None, infinity=10e20): 78 | """return the third value of the Gaussian lens formula, given any two 79 | 80 | Parameters 81 | ---------- 82 | u : float, optional 83 | object distance from first principal plane. 84 | v : float, optional 85 | image distance from rear principal plane 86 | f : float, optional 87 | focal length 88 | infinity : float 89 | numerical value to represent infinity (default=10e20) 90 | 91 | Returns 92 | ------- 93 | glfParams : namedtuple 94 | named tuple containing the Gaussian Lens Formula parameters 95 | 96 | Notes 97 | ----- 98 | Both object and image distances are considered positive. 99 | 100 | Examples 101 | -------- 102 | >>> gaussian_lens_formula(u=30, v=None, f=10) 103 | glfParams(u=30, v=15.0, f=10) 104 | >>> gaussian_lens_formula(u=30, v=15) 105 | glfParams(u=30, v=15, f=10.0) 106 | >>> gaussian_lens_formula(u=1e20, f=10) 107 | glfParams(u=1e+20, v=10.0, f=10) 108 | """ 109 | glfParams = _co.namedtuple('glfParams', ['u', 'v', 'f']) 110 | def unknown_distance(knownDistance, f): 111 | try: 112 | unknownDistance = (knownDistance * f)/(knownDistance - f) 113 | except ZeroDivisionError: 114 | unknownDistance = infinity 115 | return unknownDistance 116 | 117 | def unknown_f(u, v): 118 | return (u*v)/(u+v) 119 | 120 | if sum(i is None for i in [u, v, f]) > 1: 121 | raise ValueError('At most only one parameter can be None') 122 | 123 | if f is None: 124 | if not u or not v: 125 | raise ValueError('f cannot be determined from input') 126 | else: 127 | f = unknown_f(u, v) 128 | else: 129 | if u is None: 130 | u = unknown_distance(v, f) 131 | else: 132 | v = unknown_distance(u, f) 133 | return glfParams(u, v, f) 134 | 135 | def get_cardinal_points(ln): 136 | """Returns the distances of the cardinal points (along the optical axis). 137 | 138 | For multiple wavelengths, the distances are averaged. 139 | 140 | Parameters 141 | ---------- 142 | ln : object 143 | PyZDDE object 144 | 145 | Returns 146 | ------- 147 | fpObj : float 148 | distance of object side focal plane from surface # 1 in the LDE, 149 | irrespective of which surface is defined as the global reference 150 | fpImg : float 151 | distance of image side focal plane from IMG surface 152 | ppObj : float 153 | distance of the object side principal plane from surface # 1 in the 154 | LDE, irrespective of which surface is defined as the global 155 | reference surface 156 | ppImg : float 157 | distance of the image side principal plane from IMG 158 | 159 | Notes 160 | ----- 161 | 1. The data is consistant with the cardinal data in the Prescription file 162 | in which, the object side data is with respect to the first surface in the LDE. 163 | 2. If there are more than one wavelength, then the distances are averaged. 164 | """ 165 | zmxdir = _os.path.split(ln.zGetFile())[0] 166 | textFileName = _os.path.join(zmxdir, "tmp.txt") 167 | ln.zGetTextFile(textFileName, 'Pre', "None", 0) 168 | line_list = _pyz._readLinesFromFile(_pyz._openFile(textFileName)) 169 | ppObj, ppImg, fpObj, fpImg = 0.0, 0.0, 0.0, 0.0 170 | count = 0 171 | for line_num, line in enumerate(line_list): 172 | # Extract the Focal plane distances 173 | if "Focal Planes" in line: 174 | fpObj += float(line.split()[3]) 175 | fpImg += float(line.split()[4]) 176 | # Extract the Principal plane distances. 177 | if "Principal Planes" in line and "Anti" not in line: 178 | ppObj += float(line.split()[3]) 179 | ppImg += float(line.split()[4]) 180 | count +=1 #Increment (wavelength) counter for averaging 181 | # Calculate the average (for all wavelengths) of the principal plane distances 182 | # This is only there for extracting a single point ... ideally the design 183 | # should have just one wavelength define! 184 | if count > 0: 185 | fpObj = fpObj/count 186 | fpImg = fpImg/count 187 | ppObj = ppObj/count 188 | ppImg = ppImg/count 189 | # Delete the temporary file 190 | _pyz._deleteFile(textFileName) 191 | cardinals = _co.namedtuple('cardinals', ['Fo', 'Fi', 'Ho', 'Hi']) 192 | return cardinals(fpObj, fpImg, ppObj, ppImg) 193 | 194 | def draw_pupil_cardinal_planes(ln, firstDummySurfOff=10, cardinalSemiDia=1.2, push=True): 195 | """Insert paraxial pupil and cardinal planes surfaces in the LDE for rendering in 196 | layout plots. This is a semi-automated process; see notes. 197 | 198 | Parameters 199 | ---------- 200 | ln : object 201 | pyzdde object 202 | firstDummySurfOff : float, optional 203 | the thickness of the first dummy surface. This first dummy surface is 204 | inserted by this function. See Notes. 205 | cardinalSemiDia : float, optional 206 | semidiameter of the cardinal surfaces. (Default=1.2) 207 | push : bool 208 | push lens in the DDE server to the LDE 209 | 210 | Assumptions 211 | ----------- 212 | The function assumes that the lens is already focused appropriately, 213 | for either finite or infinite conjugate imaging. 214 | 215 | Notes 216 | ----- 217 | 1. 'first dummy surface' is a dummy surface in LDE position 1 (between the 218 | OBJ and the actual first lens surface) whose function is show the input 219 | rays to the left of the first optical surface. 220 | 2. The cardinal and pupil planes are drawn using standard surfaces in the LDE. 221 | To ensure that the ray-tracing engine does not treat these surfaces as real 222 | surfaces, we need to instruct Zemax to "ignore" rays to these surfaces. 223 | Unfortunately, we cannot do it programmatically. So, after the planes have 224 | been drawn, we need to manually do the following: 225 | 1. 2D Layout settings 226 | a. Set number of rays to 1 or as needed 227 | 2. For the pupil (ENPP and EXPP) and cardinal surfaces (H, H', F, F'), 228 | and the dummy surfaces (except for the dummy surface named "dummy 2 229 | c rays" go to "Surface Properties" >> Draw tab 230 | a. Select "Skip rays to this surface" 231 | 3. Set field points to be symmetric about the optical axis 232 | 3. For clarity, the semi-diameters of the dummy sufaces are set to zero. 233 | """ 234 | ln.zSetWave(0, 1, 1) 235 | ln.zSetWave(1, 0.55, 1) 236 | # insert dummy surface at 1 for showing the input ray 237 | ln.zRemoveVariables() 238 | # before inserting surface check to see if the object is at finite 239 | # distance. If the object is at finite distance, inserting a dummy 240 | # surface with finite thickness will change the image plane distance. 241 | # so first decrease the thickness of the object surface by the 242 | # thickness of the dummy surface 243 | objDist = ln.zGetSurfaceData(surfNum=0, code=ln.SDAT_THICK) 244 | assert firstDummySurfOff < objDist, ("dummy surf. thick ({}) must be < " 245 | "than obj dist ({})!".format(firstDummySurfOff, objDist)) 246 | if objDist < 1.0E+10: 247 | ln.zSetSurfaceData(surfNum=0, code=ln.SDAT_THICK, value=objDist - firstDummySurfOff) 248 | ln.zInsertDummySurface(surfNum=1, thick=firstDummySurfOff, semidia=0, comment='dummy 2 c rays') 249 | ln.zGetUpdate() 250 | # Draw Exit and Entrance pupil planes 251 | print("Textual information about the planes:\n") 252 | expp = ln.zGetPupil().EXPP 253 | print("Exit pupil distance from IMG:", expp) 254 | _draw_plane(ln, 'img', expp, "EXPP") 255 | 256 | enpp = ln.zGetPupil().ENPP 257 | print("Entrance pupil from Surf 1 @ LDE:", enpp) 258 | _draw_plane(ln, 'obj', enpp - firstDummySurfOff, "ENPP") 259 | 260 | # Get and draw the Principal planes 261 | fpObj, fpImg, ppObj, ppImg = get_cardinal_points(ln) 262 | 263 | print("Focal plane obj F from surf 1 @ LDE: ", fpObj, "\nFocal plane img F' from IMA: ", fpImg) 264 | _draw_plane(ln,'img', fpImg, "F'", cardinalSemiDia) 265 | _draw_plane(ln,'obj', fpObj - firstDummySurfOff, "F", cardinalSemiDia) 266 | 267 | print("Principal plane obj H from surf 1 @ LDE: ", ppObj, "\nPrincipal plane img H' from IMA: ", ppImg) 268 | _draw_plane(ln,'img', ppImg, "H'", cardinalSemiDia) 269 | _draw_plane(ln,'obj', ppObj - firstDummySurfOff, "H", cardinalSemiDia) 270 | 271 | # Check the validity of the distances 272 | ppObjToEnpp = ppObj - enpp 273 | ppImgToExpp = ppImg - expp 274 | focal = ln.zGetFirst().EFL 275 | print("Focal length: ", focal) 276 | print("Principal plane H to ENPP: ", ppObjToEnpp) 277 | print("Principal plane H' to EXPP: ", ppImgToExpp) 278 | v = gaussian_lens_formula(u=ppObjToEnpp, v=None, f=focal).v 279 | print("Principal plane H' to EXPP (abs.) " 280 | "calc. using lens equ.: ", abs(v)) 281 | ppObjTofpObj = ppObj - fpObj 282 | ppImgTofpImg = ppImg - fpImg 283 | print("Principal plane H' to rear focal plane: ", ppObjTofpObj) 284 | print("Principal plane H to front focal plane: ", ppImgTofpImg) 285 | print(("""\nCheck "Skip rays to this surface" under "Draw Tab" of the """ 286 | """surface property for the dummy and cardinal plane surfaces. """ 287 | """See Docstring Notes for details.""")) 288 | if push: 289 | ln.zPushLens(1) 290 | 291 | -------------------------------------------------------------------------------- /pyzdde/zcodes/zemaxbuttons.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #------------------------------------------------------------------------------- 3 | # Name: zemaxbuttons.py 4 | # Purpose: Class of ZEMAX 3-letter Button codes 5 | # Last updated: April 16, 2017 6 | # 7 | # Licence: MIT License 8 | # This file is subject to the terms and conditions of the MIT License. 9 | # For further details, please refer to LICENSE.txt 10 | #------------------------------------------------------------------------------- 11 | from __future__ import print_function 12 | import re as _re 13 | from pyzdde.utils.pyzddeutils import _prettifyCodeDesc, _boldifyText, _prettifyText, _print_mod 14 | 15 | class _Buttons(object): 16 | """ZEMAX 3-letter buttons. Note ZPL Macro codes are not included. 17 | The list of button codes were compiled from ZEMAX Version 13.0404, 2013. 18 | """ 19 | button_code = { 20 | "Off": "None", 21 | "ABg": "ABg Data Catalog", 22 | "Agm": "Athermal Glass Map", 23 | "Bac": "Backup To Archive File", 24 | "Bfv": "Beam File Viewer", 25 | "C31": "Src color chart 1931", 26 | "C76": "Src color chart 1976", 27 | "Caa": "Coating, Abs. vs. Angle", 28 | "Car": "Cardinal Points", 29 | "Cas": "Coat All Surfaces", 30 | "Caw": "Coating, Abs. vs. Wavelength", 31 | "Cca": "Convert to Circular Apertures", 32 | "Cda": "Coating, Dia. vs. Angle", 33 | "Cdw": "Coating, DIa. vs. Angle", 34 | "Cfa": "Convert to Floating Apertures", 35 | "Cfm": "Convert to Maximum Apertures", 36 | "Cfo": "Convert Format", 37 | "Cfs": "Chromatic Focal Shift", 38 | "Cgl": "Convert Global To Local", 39 | "Chk": "System Check", 40 | "Clg": "Convert Local to Global", 41 | "Cls": "Coating List", 42 | "Cna": "Coating, Ret. vs. Angle", 43 | "Cng": "Convert to NSC Group", 44 | "Cnw": "Coating, Ret. vs. Wavelength", 45 | "Coa": "Convert Asphere Type", 46 | "Con": "Conjugate Surface Analysis", 47 | "Cpa": "Coating, Phase vs. Angle", 48 | "Cpw": "Coating, Phase vs. Wavelength", 49 | "Cra": "Coating, Refl vs. Angle", 50 | "Crw": "Coating, Refl. vs. Wavelength", 51 | "Csd": "NSC concatenate spectral source files", 52 | "Csf": "NSC convert to spectral source file", 53 | "Cta": "Coating, Tran. vs. Angle", 54 | "Ctw": "Coating, Tran. vs. Wavelength", 55 | "Dbl": "Make Double Pass", 56 | "Dim": "Partially Coherent Image Analysis", 57 | "Dip": "Biocular Dipvergence/Convergence", 58 | "Dis": "Dispersion Plot", 59 | "Drs": "NSC download radiant source", 60 | "Dvl": "Dispersion vs. Wavelength Plot", 61 | "Dvr": "Detector Viewer", 62 | "Eca": "Explode CAD assembly", 63 | "Ect": "Edit Coating", 64 | "Ecp": "Explode Creo parametric assembly", 65 | "EDE": "Extra Data Editor", 66 | "Eec": "Export Encrypted Coating", 67 | "Eia": "Explode inventor assembly", 68 | "Ele": "ZEMAX Element Drawing", 69 | "Enc": "Diff Encircled Energy", 70 | "Esa": "Explode solidworks assembly", 71 | "Ext": "Exit", 72 | "Fba": "Find Best Asphere", 73 | "Fcd": "Field Curv/Distorion", 74 | "Fcl": "Fiber Coupling", 75 | "Fie": "Field Data", 76 | "Fld": "Add Fold Mirror", 77 | "Flx": "Delete Fold Mirror", 78 | "Fmm": "FFT MTF Map", 79 | "Foa": "Foucault Analysis", 80 | "Foo": "Footprint Analysis", 81 | "Fov": "Biocular Field of View Analysis", 82 | "Fps": "FFT PSF", 83 | "Fvw": "Flux vs. Wavelength", 84 | "Gbp": "Parax Gaussian Beam", 85 | "Gbs": "Skew Gaussian Beam", 86 | "Gcp": "Glass Compare", 87 | "Gee": "Geometric Encircled Energy", 88 | "Gen": "General Lens Data", 89 | "Gft": "Glass Fit", 90 | "Gho": "Ghost Focus", 91 | "Gip": "Grin Profile", 92 | "Gla": "Glass Catalog", 93 | "Glb": "Global Optimization", 94 | "Gmf": "Generate MAT file", 95 | "Gmm": "Geometric MTF Map", 96 | "Gmp": "Glass Map", 97 | "Gpr": "Gradium Profile", 98 | "Grd": "Grid Distortion", 99 | "Grs": "NSC generate radiant source", 100 | "Gst": "Glass Substitution Template", 101 | "Gtf": "Geometric MTF", 102 | "Gvf": "Geometric MTF vs. Field", 103 | "Ham": "Hammer Optimization", 104 | "Hcs": "Huygens PSF Cross Section", 105 | "Hlp": "Help", 106 | "Hmf": "Huygens MTF", 107 | "Hmh": "Huygens MTF vs Field", 108 | "Hps": "Huygens PSF", 109 | "Hsm": "Huygens Surface MTF", 110 | "Htf": "Huygens Through Focus MTF", 111 | "ISO": "ISO Element Drawing", 112 | "Ibm": "Geometric Bitmap Image Analysis", 113 | "Igs": "Export IGES File", 114 | "Iht": "Incident Angle vs. Image Height", 115 | "Ilf": "Illumination Surface", 116 | "Ils": "Illumination Scan", 117 | "Ima": "Geometric Image Analysis", 118 | "Imv": "IMA/BIM File Viewer", 119 | "Ins": "Insert Lens", 120 | "Int": "Interferogram", 121 | "Jbv": "Bitmap File Viewer", 122 | "L3d": "3D Layout", 123 | "L3n": "NSC 3D Layout", 124 | "LDE": "Lens Data Editor", 125 | "LSn": "NSC Shaded Model Layout", 126 | "Lac": "Last Configuration", 127 | "Lat": "Lateral Color", 128 | "Lay": "2D Layout", 129 | "Len": "Lens Search", 130 | "Lin": "Line/Edge Response", 131 | "Lok": "Lock All Windows", 132 | "Lon": "Longitudinal Aberration", 133 | "Lsa": "Light Source Analysis", 134 | "Lsf": "FFT Line/Edge Spread", 135 | "Lsh": "Shaded Model Layout", 136 | "Lsm": "Solid Model Layout", 137 | "Ltr": "NSC lighting trace", 138 | "Lwf": "Wireframe Layout", 139 | "MCE": "Multi-Config Editor", 140 | "MFE": "Merit Function Editor", 141 | "Mfl": "Merit Function List", 142 | "Mfo": "Make Focal", 143 | "Mtf": "Modulation Transfer Function (FFT MTF)", 144 | "Mth": "MTF vs. Field", 145 | "NCE": "Non-Sequential Editor", 146 | "New": "New File", 147 | "Nxc": "Next Configuration", 148 | "Obv": "NSC Object Viewer", 149 | "Opd": "Opd Fan", 150 | "Ope": "Open File", 151 | "Opt": "Optimization", 152 | "Pab": "Pupil Aberration Fan", 153 | "Pal": "Power Field Map", 154 | "Pat": "ZRD Path Analysis", 155 | "Pci": "Partially Coherent Image Analysis", 156 | "Pcs": "PSF Cross Section", 157 | "Per": "Performance Test", 158 | "Pha": "Pol. Phase Aberration", 159 | "Pmp": "Pol. Pupil Map", 160 | "Pol": "Pol. Ray Trace", 161 | "Pop": "Physical Optics Propagation", 162 | "Ppm": "Power Pupil Map", 163 | "Pre": "Prescription Data", 164 | "Prf": "Preferences", 165 | "Ptf": "Pol. Transmission Fan", 166 | "Pvr": "CAD part viewer", 167 | "Pzd": "Playback ZRD on Detectors", 168 | "Qad": "Quick Adjust", 169 | "Qfo": "Quick Focus", 170 | "Raa": "Remove All Apertures", 171 | "Ray": "Ray Fan", 172 | "Rcf": "Reload Coating File", 173 | "Rda": "NSC reverse radiance analysis", 174 | "Rdb": "Ray Database", 175 | "Rdw": "NSC roadway lighting analysis", 176 | "Red": "Redo", 177 | "Rel": "Relative Illumination", 178 | "Res": "Restore From Archive File", 179 | "Rev": "Reverse Elements", 180 | "Rfm": "RMS Field Map", 181 | "Rg4": "New Report Graphic 4", 182 | "Rg6": "New Report Graphic 6", 183 | "Rmf": "RMS vs. Focus", 184 | "Rml": "Refresh Macro List", 185 | "Rms": "RMS vs. Field", 186 | "Rmw": "RMS vs. Wavelength", 187 | "Rtc": "Ray Trace Control", 188 | "Rtr": "Ray Trace", 189 | "Rva": "Remove Variables", 190 | "Rxl": "Refresh Extensions List", 191 | "Sag": "Sag Table", 192 | "Sas": "Save As", 193 | "Sav": "Save File", 194 | "Sca": "Scale Lens", 195 | "Scc": "Surface Curvature Cross Section", 196 | "Scv": "Surface Curvature", 197 | "Sdi": "Seidel Diagram", 198 | "Sdv": "Src directivity", 199 | "Sei": "Seidel Coefficients", 200 | "Sff": "Full Field Spot", 201 | "Sfv": "Scatter Function Viewer", 202 | "Sim": "Image Simulation", 203 | "Sld": "Slider", 204 | "Slm": "Stock lens matching", 205 | "Sma": "Spot Matrix", 206 | "Smc": "Spot Matrix Config", 207 | "Smf": "Surface MTF", 208 | "Spc": "Surface Phase Cross Section", 209 | "Spj": "Src projection", 210 | "Spo": "Src polar", 211 | "Spt": "Spot Diagram", 212 | "Spv": "Scatter Polar Plot", 213 | "Srp": "Surface Phase", 214 | "Srs": "Surface Sag", 215 | "Ssc": "Surface Sag Cross Section", 216 | "Ssg": "System Summary Graphic", 217 | "Srv": "Src rms viewer", 218 | "Ssp": "src spectrum", 219 | "Stf": "Though Focus Spot", 220 | "Sti": "NSC convert SDF to IES", 221 | "Sur": "Surface Data", 222 | "Sys": "System Data", 223 | "TDE": "Tolerance Data Editor", 224 | "Tde": "Tilt/Decenter Elements", 225 | "Tfg": "Through Focus GTF", 226 | "Tfm": "Through Focus MTF", 227 | "Tls": "Tolerance List", 228 | "Tol": "Tolerancing", 229 | "Tpf": "Test Plate Fit", 230 | "Tpl": "Test Plate Lists", 231 | "Tra": "Pol. Transmission", 232 | "Trw": "Transmission vs. Wavelength", 233 | "Tsm": "Tolerance Summary", 234 | "Un2": "Universal Plot 2D", 235 | "Und": "Undo", 236 | "Uni": "Universal Plot", 237 | "Unl": "Unlock All Windows", 238 | "Upa": "Update All", 239 | "Upd": "Update", 240 | "Vig": "Vignetting Plot", 241 | "Vop": "Visual Optimization", 242 | "Vra": "Make All Radii Variable", 243 | "Vth": "Make All Thickness Variable", 244 | "Wav": "Wavelength Data", 245 | "Wfm": "Wavefront Map", 246 | "Xdi": "Extended Diffraction Image Ana", 247 | "Xis": "Export IGES/STEP/SAT FIle", 248 | "Xse": "Extended Source Encircled", 249 | "Yni": "YNI Contributions", 250 | "Yyb": "Y-Ybar", 251 | "Zat": "Zernike Annular Terms", 252 | "Zbb": "Export Zemax Black Box Data", 253 | "Zex": "ZEMAX Extensions", 254 | "Zfr": "Zernike Fringe Terms", 255 | "Zpd": "Zemax Part Designer", 256 | "Zpl": "Edit/Run ZPL Macros", 257 | "Zst": "Zernike Standard Terms", 258 | "Zvf": "Zernike Coefficients vs. Field" 259 | } 260 | 261 | def showZButtonList(): 262 | """List all the button codes 263 | 264 | showZButtonList()->None (the button codes are printed on screen) 265 | 266 | """ 267 | print("Listing all ZEMAX Button codes:") 268 | for code, description in sorted(_Buttons.button_code.items()): 269 | _print_mod(_prettifyCodeDesc(code,description)) 270 | _print_mod(_boldifyText("Total number of buttons = ",str(len(_Buttons.button_code)))) 271 | 272 | def getZButtonCount(): 273 | """Returns the total number of buttons 274 | 275 | getZButtonCount()->count 276 | 277 | """ 278 | return len(_Buttons.button_code) 279 | 280 | def isZButtonCode(buttonCode): 281 | """Returns True or False depending on whether the button code is a valid 282 | button code. 283 | 284 | isZButtonCode(buttonCode)->bool 285 | 286 | Parameters 287 | ---------- 288 | buttonCode : (string) the 3-letter case-sensitive button code to validate 289 | 290 | Returns 291 | ------- 292 | bool : True if valid button code, False otherwise 293 | """ 294 | return str(buttonCode) in _Buttons.button_code.keys() 295 | 296 | def showZButtonDescription(buttonCode): 297 | """Get a short description about a button code. 298 | 299 | showZButtonDescription(buttonCode)->description 300 | 301 | Parameters 302 | ---------- 303 | buttonCode : (string) a 3-letter button code 304 | 305 | Returns 306 | ------- 307 | description : a shot description about the button code function/analysis type. 308 | """ 309 | if isZButtonCode(str(buttonCode)): 310 | _print_mod(_prettifyText(str(buttonCode), " is a ZEMAX button code", 311 | color0='magenta',color1='black')) 312 | _print_mod(_prettifyText("Description: ", _Buttons.button_code[str(buttonCode)], 313 | color0='blue',color1='black')) 314 | else: 315 | print("{} is NOT a valid ZEMAX button code.".format(str(buttonCode))) 316 | 317 | def findZButtonCode(keywords): 318 | """Find Zemax button codes using specific keywords of interest. 319 | 320 | findZButtonCode("keyword#1 [, keyword#2, keyword#3, ...]")->searchResult 321 | 322 | Parameters 323 | ---------- 324 | keywords : a string containing a list of comma separated keywords. 325 | 326 | Example 327 | ------- 328 | >>> zb.findZButtonCode("Zernike") 329 | [Zst] Zernike Standard Terms 330 | [Zvf] Zernike Coefficients vs. Field 331 | [Zfr] Zernike Fringe Terms 332 | [Zat] Zernike Annular Terms 333 | 334 | Found 4 Button codes. 335 | 336 | >>> zb.findZButtonCode("Fan") 337 | [Opd] Opd Fan 338 | [Ray] Ray Fan 339 | [Ptf] Pol. Transmission Fan 340 | [Pab] Pupil Aberration Fan 341 | 342 | Found 4 Button codes. 343 | """ 344 | words2find = [words.strip() for words in keywords.split(",")] 345 | previousFoundKeys = [] 346 | for button, description in _Buttons.button_code.items(): 347 | for kWord in words2find: 348 | if __find(kWord,description): 349 | _print_mod(_prettifyCodeDesc(button,description)) 350 | previousFoundKeys.append(button) 351 | break # break the inner for loop 352 | if previousFoundKeys: 353 | _print_mod(_boldifyText("Found ", str(len(previousFoundKeys)), 354 | " Button codes",'blue','red','blue')) 355 | 356 | def __find(word2find,instring): 357 | r = _re.compile(r'\b({0})s?\b'.format(word2find),flags=_re.IGNORECASE) 358 | if r.search(instring): 359 | return True 360 | else: 361 | return False 362 | 363 | if __name__ == '__main__': 364 | #Test showZButtonList() 365 | showZButtonList() 366 | #Test getZButtonCount() 367 | print("Total number of buttons:",getZButtonCount()) 368 | #Test isZButtonCode() 369 | print("'Pre' is a button code (True/False):", isZButtonCode('Pre')) 370 | print("'Wav' is an button code (True/False):", isZButtonCode('Wav')) 371 | print("'RANDOM' is a button code (True/False):", isZButtonCode('RANDOM')) 372 | #Test showZOperandDescription() 373 | showZButtonDescription('RANDOM') 374 | showZButtonDescription('Vth') 375 | 376 | 377 | -------------------------------------------------------------------------------- /pyzdde/arraytrace/arrayTraceClient.c: -------------------------------------------------------------------------------- 1 | // The code here has been adapted from the C programs zclient.c and ArrayDemo.c, 2 | // which were originally written by Kenneth Moore, and they are shipped with Zemax. 3 | // zclient.c 4 | // Originally written by Kenneth Moore June 1997 5 | // Copyright 1997-2006 Kenneth Moore 6 | // ArrayDemo.c sample program 7 | // Written by Kenneth Moore March 1999 8 | // The original zclient.c and ArrayDemo.c files are also available in the same 9 | // directory for reference 10 | 11 | #include "arrayTraceClient.h" 12 | 13 | /* global variables used by the client code */ 14 | char szAppName[] = "ZemaxClient"; 15 | int GotData, ngNumRays, ZEMAX_INSTANCE = 0; 16 | char szGlobalBuffer[5000], szCommandLine[260]; 17 | HINSTANCE globalhInstance; 18 | HWND hwndServer, hwndClient; 19 | DDERAYDATA *rdpGRD = NULL; 20 | DDERAYDATA *gPtr2RD = NULL; /* used for passing the ray data array to the user function */ 21 | unsigned int DdeTimeout; 22 | int RETVAL = 0; /* Return value to Python indicating general error conditions*/ 23 | /* 0 = SUCCESS, -1 = Couldn't retrieve data in PostArrayTraceMessage, 24 | -999 = Couldn't communicate with Zemax, -998 = timeout reached, etc*/ 25 | 26 | BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) 27 | { 28 | switch (ul_reason_for_call) 29 | { 30 | case DLL_PROCESS_ATTACH: 31 | globalhInstance = (HINSTANCE)hModule; 32 | break; 33 | case DLL_THREAD_ATTACH: 34 | case DLL_THREAD_DETACH: 35 | case DLL_PROCESS_DETACH: 36 | break; 37 | } 38 | return TRUE; 39 | } 40 | 41 | 42 | void rayTraceFunction(void) 43 | { 44 | static char szBuffer[5000]; 45 | int ret = 0; 46 | ret = PostArrayTraceMessage(szBuffer, gPtr2RD); 47 | RETVAL = ret; /* ret = -1, if couldn't get data*/ 48 | /* clear the pointer */ 49 | gPtr2RD = NULL; 50 | } 51 | 52 | int __stdcall arrayTrace(DDERAYDATA * pRAD, unsigned int timeout) 53 | { 54 | HWND hwnd; /* handle to client window */ 55 | MSG msg; 56 | WNDCLASSEX wndclass; 57 | 58 | wndclass.cbSize = sizeof(wndclass); 59 | wndclass.style = CS_HREDRAW | CS_VREDRAW; 60 | wndclass.lpfnWndProc = WndProc; 61 | wndclass.cbClsExtra = 0; 62 | wndclass.cbWndExtra = 0; 63 | wndclass.hInstance = globalhInstance; 64 | wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); 65 | wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); 66 | wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); 67 | wndclass.lpszMenuName = NULL; 68 | wndclass.lpszClassName = szAppName; 69 | wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); 70 | RegisterClassEx(&wndclass); 71 | 72 | /* assign the pointer to ray data a global pointer so that rayTraceFunction() can access it*/ 73 | gPtr2RD = pRAD; 74 | 75 | if (timeout > DDE_TIMEOUT) 76 | DdeTimeout = timeout; 77 | else 78 | DdeTimeout = DDE_TIMEOUT; 79 | 80 | hwnd = CreateWindow(szAppName, "ZEMAX Client", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 81 | CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, globalhInstance, NULL); 82 | UpdateWindow(hwnd); 83 | SendMessage(hwnd, WM_USER_INITIATE, 0, 0L); 84 | 85 | while (GetMessage(&msg, NULL, 0, 0)) 86 | { 87 | TranslateMessage(&msg); 88 | DispatchMessage(&msg); 89 | } 90 | return RETVAL; 91 | } 92 | 93 | LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) 94 | { 95 | ATOM aApp, aTop, aItem; 96 | DDEACK DdeAck; 97 | DDEDATA *pDdeData; 98 | GLOBALHANDLE hDdeData; 99 | WORD wStatus; 100 | UINT_PTR uiLow, uiHi; 101 | 102 | switch (iMsg) 103 | { 104 | case WM_CREATE: 105 | hwndServer = 0; 106 | return 0; 107 | 108 | case WM_USER_INITIATE: 109 | /* find ZEMAX */ 110 | 111 | // code added September 1, 2006 to identify which ZEMAX is calling us, in case more than 1 copy of ZEMAX is running 112 | // this currently is only supported by user defined operands. 113 | // aApp = GlobalAddAtom ("ZEMAX"); 114 | if (1) 115 | { 116 | char szSub[500]; 117 | GetString(szCommandLine, 5, szSub); 118 | if (strcmp(szSub, "ZEMAX1") == 0 || strcmp(szSub, "ZEMAX2") == 0) 119 | { 120 | if (strcmp(szSub, "ZEMAX1") == 0) 121 | { 122 | aApp = GlobalAddAtom("ZEMAX1"); 123 | ZEMAX_INSTANCE = 1; 124 | } 125 | if (strcmp(szSub, "ZEMAX2") == 0) 126 | { 127 | aApp = GlobalAddAtom("ZEMAX2"); 128 | ZEMAX_INSTANCE = 2; 129 | } 130 | } 131 | else 132 | { 133 | aApp = GlobalAddAtom("ZEMAX"); 134 | ZEMAX_INSTANCE = 0; 135 | } 136 | } 137 | 138 | aTop = GlobalAddAtom("RayData"); 139 | 140 | //SendMessage(HWND_BROADCAST, WM_DDE_INITIATE, (WPARAM)hwnd, MAKELONG(aApp, aTop)); 141 | SendMessage(HWND_BROADCAST, WM_DDE_INITIATE, (WPARAM)hwnd, PackDDElParam(WM_DDE_INITIATE, aApp, aTop)); 142 | 143 | /* delete the atoms */ 144 | GlobalDeleteAtom(aApp); 145 | GlobalDeleteAtom(aTop); 146 | 147 | /* If no response, terminate */ 148 | if (hwndServer == NULL) 149 | { 150 | printf("\nCannot communicate with ZEMAX! Zemax may not be running!\n"); 151 | //MessageBox(hwnd, "Cannot communicate with ZEMAX!", "ERROR", MB_ICONEXCLAMATION | MB_OK); 152 | DestroyWindow(hwnd); 153 | RETVAL = -999; 154 | return 0; 155 | } 156 | 157 | hwndClient = hwnd; 158 | 159 | rayTraceFunction(); 160 | 161 | /* terminate the DDE connection */ 162 | PostMessage(hwndServer, WM_DDE_TERMINATE, (WPARAM)hwnd, 0L); 163 | hwndServer = NULL; 164 | 165 | /* now TERMINATE! */ 166 | DestroyWindow(hwnd); 167 | return 0; 168 | 169 | case WM_DDE_DATA: 170 | /* here comes the data! */ 171 | // wParam -- sending window handle 172 | // lParam -- DDEDATA memory handle & item atom 173 | UnpackDDElParam(WM_DDE_DATA, lParam, &uiLow, &uiHi); 174 | FreeDDElParam(WM_DDE_DATA, lParam); 175 | hDdeData = (GLOBALHANDLE)uiLow; 176 | pDdeData = (DDEDATA *)GlobalLock(hDdeData); 177 | aItem = (ATOM)uiHi; 178 | 179 | // Initialize DdeAck structure 180 | DdeAck.bAppReturnCode = 0; 181 | DdeAck.reserved = 0; 182 | DdeAck.fBusy = FALSE; 183 | DdeAck.fAck = FALSE; 184 | 185 | // Check for matching format, put the data in the buffer 186 | if (pDdeData->cfFormat == CF_TEXT) 187 | { 188 | /* get the data back into RD */ 189 | if (rdpGRD) 190 | memcpy(rdpGRD, (DDERAYDATA *)pDdeData->Value, (ngNumRays + 1)*sizeof(DDERAYDATA)); 191 | else 192 | strcpy(szGlobalBuffer, (char *)pDdeData->Value); 193 | } 194 | 195 | GotData = 1; 196 | GlobalDeleteAtom(aItem); 197 | 198 | // Acknowledge if necessary 199 | if (pDdeData->fAckReq == TRUE) 200 | { 201 | wStatus = *((WORD *)&DdeAck); 202 | if (!PostMessage((HWND)wParam, WM_DDE_ACK, (WPARAM)hwnd, PackDDElParam(WM_DDE_ACK, wStatus, aItem))) 203 | { 204 | if (hDdeData) 205 | { 206 | GlobalUnlock(hDdeData); 207 | GlobalFree(hDdeData); 208 | } 209 | return 0; 210 | } 211 | } 212 | 213 | // Clean up 214 | GlobalUnlock(hDdeData); 215 | if (pDdeData->fRelease == TRUE || DdeAck.fAck == FALSE) GlobalFree(hDdeData); 216 | return 0; 217 | 218 | case WM_DDE_ACK: 219 | /* we are receiving an acknowledgement */ 220 | /* the only one we care about is in response to the WM_DDE_INITIATE; otherwise free just the memory */ 221 | if (hwndServer == NULL) 222 | { 223 | uiLow = (UINT_PTR)NULL; 224 | uiHi = (UINT_PTR)NULL; 225 | //UnpackDDElParam(WM_DDE_ACK, lParam, &uiLow, &uiHi); /* Was causing memory error right after the SendMessage() call from case WM_USER_INITIATE:*/ 226 | //FreeDDElParam(WM_DDE_ACK, lParam); /* Was causing memory error right after the SendMessage() call from case WM_USER_INITIATE:*/ 227 | uiLow = (UINT_PTR)(((UINT)lParam) & 0xffff); 228 | uiHi = (UINT_PTR)((((UINT)lParam) >> 16) & 0xffff); 229 | hwndServer = (HWND)wParam; 230 | if (uiLow) GlobalDeleteAtom((ATOM)uiLow); 231 | if (uiHi) GlobalDeleteAtom((ATOM)uiHi); 232 | } 233 | else 234 | { 235 | HWND dummy; 236 | uiLow = (UINT_PTR)NULL; 237 | uiHi = (UINT_PTR)NULL; 238 | //UnpackDDElParam(WM_DDE_ACK, lParam, &uiLow, &uiHi); 239 | //FreeDDElParam(WM_DDE_ACK, lParam); 240 | uiLow = (UINT_PTR)(((UINT)lParam) & 0xffff); 241 | uiHi = (UINT_PTR)((((UINT)lParam) >> 16) & 0xffff); 242 | dummy = (HWND)wParam; 243 | if (uiLow) GlobalDeleteAtom((ATOM)uiLow); 244 | if (uiHi) GlobalDeleteAtom((ATOM)uiHi); 245 | } 246 | return 0; 247 | 248 | case WM_DDE_TERMINATE: 249 | PostMessage(hwndServer, WM_DDE_TERMINATE, (WPARAM)hwnd, 0L); 250 | hwndServer = NULL; 251 | return 0; 252 | 253 | case WM_PAINT: 254 | { 255 | PAINTSTRUCT ps; 256 | BeginPaint(hwnd, &ps); 257 | EndPaint(hwnd, &ps); 258 | } 259 | return 0; 260 | 261 | case WM_CLOSE: 262 | PostMessage(hwndServer, WM_DDE_TERMINATE, (WPARAM)hwnd, 0L); 263 | break; // for default processing 264 | 265 | case WM_DESTROY: 266 | PostQuitMessage(0); 267 | return 0; 268 | } 269 | return DefWindowProc(hwnd, iMsg, wParam, lParam); 270 | } 271 | 272 | void WaitForData(HWND hwnd) 273 | { 274 | int sleep_count; 275 | MSG msg; 276 | DWORD dwTime; 277 | dwTime = GetCurrentTime(); 278 | 279 | sleep_count = 0; 280 | 281 | while (!GotData) 282 | { 283 | while (PeekMessage(&msg, hwnd, WM_DDE_FIRST, WM_DDE_LAST, PM_REMOVE)) 284 | { 285 | DispatchMessage(&msg); 286 | } 287 | /* Give the server a chance to respond */ 288 | Sleep(0); 289 | sleep_count++; 290 | if (sleep_count > 10000) 291 | { 292 | if (GetCurrentTime() - dwTime > DdeTimeout) 293 | { 294 | printf("\nTimeout reached!\n"); /* will be visible in stdout*/ 295 | RETVAL = -998; /* indicate to python calling function */ 296 | return; 297 | } 298 | sleep_count = 0; 299 | } 300 | } 301 | } 302 | 303 | char * GetString(char *szBuffer, int n, char *szSubString) 304 | { 305 | int i, j, k; 306 | char szTest[5000]; 307 | 308 | szSubString[0] = '\0'; 309 | i = 0; 310 | j = 0; 311 | k = 0; 312 | while (szBuffer[i] && (k <= n)) 313 | { 314 | szTest[j] = szBuffer[i]; 315 | 316 | if (szBuffer[i] == '"') 317 | { 318 | i++; 319 | j++; 320 | szTest[j] = szBuffer[i]; 321 | 322 | /* we have a double quote; keep reading until EOF or another double quote */ 323 | while (szBuffer[i] != '"' && szBuffer[i]) 324 | { 325 | i++; 326 | j++; 327 | szTest[j] = szBuffer[i]; 328 | } 329 | } 330 | 331 | if (szTest[j] == ' ' || szTest[j] == '\n' || szTest[j] == '\r' || szTest[j] == '\0' || szTest[j] == ',') 332 | { 333 | szTest[j] = '\0'; 334 | if (k == n) 335 | { 336 | strcpy(szSubString, szTest); 337 | return szSubString; 338 | } 339 | k++; 340 | j = -1; 341 | } 342 | i++; 343 | j++; 344 | } 345 | 346 | szTest[j] = '\0'; 347 | if (k == n) strcpy(szSubString, szTest); 348 | 349 | return szSubString; 350 | } 351 | 352 | int PostArrayTraceMessage(char *szBuffer, DDERAYDATA *RD) 353 | { 354 | ATOM aItem; 355 | HGLOBAL hPokeData; 356 | DDEPOKE * lpPokeData; 357 | long numbytes; 358 | int numrays; 359 | 360 | if (RD[0].opd > 4) 361 | { 362 | /* NSC Rays */ 363 | numrays = (int)RD[0].opd - 5; 364 | } 365 | else 366 | { 367 | /* sequential rays */ 368 | numrays = RD[0].error; 369 | } 370 | 371 | /* point to where the data is */ 372 | rdpGRD = RD; 373 | ngNumRays = numrays; 374 | 375 | numbytes = (1 + numrays)*sizeof(DDERAYDATA); 376 | hPokeData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, (LONG) sizeof(DDEPOKE) + numbytes); 377 | lpPokeData = (DDEPOKE *)GlobalLock(hPokeData); 378 | lpPokeData->fRelease = TRUE; 379 | lpPokeData->cfFormat = CF_TEXT; 380 | memcpy(lpPokeData->Value, RD, numbytes); 381 | 382 | /* clear the buffers */ 383 | szGlobalBuffer[0] = '\0'; 384 | szBuffer[0] = '\0'; 385 | 386 | aItem = GlobalAddAtom("RayArrayData"); 387 | GlobalUnlock(hPokeData); 388 | 389 | GotData = 0; 390 | 391 | if (!PostMessage(hwndServer, WM_DDE_POKE, (WPARAM)hwndClient, PackDDElParam(WM_DDE_POKE, (UINT)hPokeData, aItem))) 392 | { 393 | //MessageBox(hwndClient, "Cannot communicate with ZEMAX!", "Hello?", MB_ICONEXCLAMATION | MB_OK); 394 | printf("\nCannot communicate with ZEMAX! Cannot trace rays!\n"); 395 | GlobalDeleteAtom(aItem); 396 | GlobalFree(hPokeData); 397 | RETVAL = -999; 398 | return -1; 399 | } 400 | GlobalDeleteAtom(aItem); 401 | WaitForData(hwndClient); 402 | strcpy(szBuffer, szGlobalBuffer); 403 | 404 | /* clear the pointer */ 405 | rdpGRD = NULL; 406 | 407 | if (GotData) 408 | return 0; 409 | else 410 | return -1; 411 | } 412 | -------------------------------------------------------------------------------- /pyzdde/arraytrace/zclient.c: -------------------------------------------------------------------------------- 1 | // Zclient: ZEMAX client template program 2 | // Originally written by Kenneth Moore June 1997 3 | // Copyright 1997-2006 Kenneth Moore 4 | // 5 | // Normally, none of this code needs to be modified. Simply include this file and 6 | // compile and link with the code that contains "UserFunction". 7 | // The zclient program is responsible for establishing communication 8 | // with the ZEMAX server. All data from ZEMAX can be obtained by calling 9 | // PostRequestMessage or PostArrayTraceMessage with the item name and a buffer to hold the data. 10 | // 11 | // Zclient will call UserFunction when the DDE communication link is established and ready. 12 | // Zclient will automatically terminate the connection when UserFunction returns. 13 | // 14 | // Version 1.1 modified to support Array ray tracing September, 1997 15 | // Version 1.2 modified for faster execution October, 1997 16 | // Version 1.3 modified for faster execution November, 1997 17 | // Version 1.4 modified to fix memory leak January, 1998 18 | // Version 1.5 modified to add support for long path names and quotes November, 1998 19 | // Version 1.6 modified to fix missing support for long path names and quotes in MakeEmptyWindow March, 1999 20 | // Version 1.7 modified to fix memory leak in WM_DDE_ACK, March 1999 21 | // Version 1.8 modified to add E-field data to DDERAYDATA for ZEMAX 10.0, December 2000 22 | // Version 1.9 modified PostRequestMessage and PostArrayTraceMessage to return -1 if data failed (usually because of a timeout) or 0 otherwise, April 2001 23 | // Version 2.0 modified WM_USER_INITIATE to distingush between 2 possibly simultaneous copies of ZEMAX running when responding to UDOP calls, September 1, 2006 24 | // Version 2.1 modified to support Visual Studio 2005. Added the #pragma to disable the warning about deprecated functions 25 | // Version 2.2 modified to move GotData=0 to more robust position. If ZEMAX returns data very quickly a deadlock can occur. November 30, 2007 26 | 27 | // Version 2.3 modified the typecast of uiLow and uiHi from UINT to UINT_PTR. CODE ONLY WORKS IN 64-BIT (Debug or Release). Recast (int) msg.wParam as a return argument for WINAPI 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #define WM_USER_INITIATE (WM_USER + 1) 37 | #define DDE_TIMEOUT 50000 38 | #pragma warning ( disable : 4996 ) // functions like strcpy are now deprecated for security reasons; this disables the warning 39 | 40 | typedef struct 41 | { 42 | double x, y, z, l, m, n, opd, intensity; 43 | double Exr, Exi, Eyr, Eyi, Ezr, Ezi; 44 | int wave, error, vigcode, want_opd; 45 | }DDERAYDATA; 46 | 47 | LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); 48 | void WaitForData(HWND hwnd); 49 | char *GetString(char *szBuffer, int n, char *szSubString); 50 | void remove_quotes(char *s); 51 | int PostRequestMessage(char *szItem, char *szBuffer); 52 | int PostArrayTraceMessage(char *szBuffer, DDERAYDATA *RD); 53 | void CenterWindow(HWND hwnd); 54 | void UserFunction(char *szCommandLine); 55 | void MakeEmptyWindow(int text, char *szAppName, char *szOptions); 56 | void Get_2_5_10(double cmax, double *cscale); 57 | 58 | /* global variables used by the client code */ 59 | char szAppName[] = "ZemaxClient"; 60 | int GotData, ngNumRays, ZEMAX_INSTANCE = 0; 61 | char szGlobalBuffer[5000], szCommandLine[260]; 62 | HINSTANCE globalhInstance; 63 | HWND hwndServer, hwndClient; 64 | DDERAYDATA *rdpGRD = NULL; 65 | 66 | int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) 67 | { 68 | HWND hwnd; 69 | MSG msg; 70 | WNDCLASSEX wndclass; 71 | 72 | wndclass.cbSize = sizeof (wndclass); 73 | wndclass.style = CS_HREDRAW | CS_VREDRAW; 74 | wndclass.lpfnWndProc = WndProc; 75 | wndclass.cbClsExtra = 0; 76 | wndclass.cbWndExtra = 0; 77 | wndclass.hInstance = hInstance; 78 | wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION); 79 | wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); 80 | wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); 81 | wndclass.lpszMenuName = NULL; 82 | wndclass.lpszClassName = szAppName; 83 | wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION); 84 | RegisterClassEx (&wndclass); 85 | 86 | globalhInstance = hPrevInstance; 87 | 88 | if (iCmdShow) 89 | { 90 | // do nothing. This argument is unused, and is only referenced here to avoid a compiler warning about unused function arguments. 91 | } 92 | 93 | strcpy(szCommandLine, szCmdLine); 94 | 95 | hwnd = CreateWindow (szAppName, "ZEMAX Client", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); 96 | UpdateWindow (hwnd); 97 | SendMessage (hwnd, WM_USER_INITIATE, 0, 0L); 98 | 99 | while (GetMessage (&msg, NULL, 0, 0)) 100 | { 101 | TranslateMessage (&msg); 102 | DispatchMessage (&msg); 103 | } 104 | return (int) msg.wParam; 105 | } 106 | 107 | LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) 108 | { 109 | ATOM aApp, aTop, aItem; 110 | DDEACK DdeAck; 111 | DDEDATA *pDdeData; 112 | GLOBALHANDLE hDdeData; 113 | WORD wStatus; 114 | UINT_PTR uiLow, uiHi; 115 | 116 | switch (iMsg) 117 | { 118 | case WM_CREATE : 119 | hwndServer = 0; 120 | return 0; 121 | 122 | case WM_USER_INITIATE : 123 | /* find ZEMAX */ 124 | 125 | // code added September 1, 2006 to identify which ZEMAX is calling us, in case more than 1 copy of ZEMAX is running 126 | // this currently is only supported by user defined operands. 127 | // aApp = GlobalAddAtom ("ZEMAX"); 128 | if (1) 129 | { 130 | char szSub[500]; 131 | GetString(szCommandLine, 5, szSub); 132 | if (strcmp(szSub,"ZEMAX1") == 0 || strcmp(szSub,"ZEMAX2") == 0) 133 | { 134 | if (strcmp(szSub,"ZEMAX1") == 0) 135 | { 136 | aApp = GlobalAddAtom ("ZEMAX1"); 137 | ZEMAX_INSTANCE = 1; 138 | } 139 | if (strcmp(szSub,"ZEMAX2") == 0) 140 | { 141 | aApp = GlobalAddAtom ("ZEMAX2"); 142 | ZEMAX_INSTANCE = 2; 143 | } 144 | } 145 | else 146 | { 147 | aApp = GlobalAddAtom ("ZEMAX"); 148 | ZEMAX_INSTANCE = 0; 149 | } 150 | } 151 | 152 | aTop = GlobalAddAtom ("RayData"); 153 | 154 | SendMessage (HWND_BROADCAST, WM_DDE_INITIATE, (WPARAM) hwnd, MAKELONG (aApp, aTop)); 155 | 156 | /* delete the atoms */ 157 | GlobalDeleteAtom (aApp); 158 | GlobalDeleteAtom (aTop); 159 | 160 | /* If no response, terminate */ 161 | if (hwndServer == NULL) 162 | { 163 | MessageBox (hwnd, "Cannot communicate with ZEMAX!", "Hello?", MB_ICONEXCLAMATION | MB_OK); 164 | DestroyWindow(hwnd); 165 | return 0; 166 | } 167 | 168 | hwndClient = hwnd; 169 | 170 | UserFunction(szCommandLine); 171 | 172 | /* terminate the DDE connection */ 173 | PostMessage(hwndServer, WM_DDE_TERMINATE, (WPARAM) hwnd, 0L); 174 | hwndServer = NULL; 175 | 176 | /* now TERMINATE! */ 177 | DestroyWindow(hwnd); 178 | return 0; 179 | 180 | case WM_DDE_DATA : 181 | /* here comes the data! */ 182 | // wParam -- sending window handle 183 | // lParam -- DDEDATA memory handle & item atom 184 | UnpackDDElParam(WM_DDE_DATA, lParam, &uiLow, &uiHi); 185 | FreeDDElParam(WM_DDE_DATA, lParam); 186 | hDdeData = (GLOBALHANDLE) uiLow; 187 | pDdeData = (DDEDATA *) GlobalLock (hDdeData); 188 | aItem = (ATOM) uiHi; 189 | 190 | // Initialize DdeAck structure 191 | DdeAck.bAppReturnCode = 0; 192 | DdeAck.reserved = 0; 193 | DdeAck.fBusy = FALSE; 194 | DdeAck.fAck = FALSE; 195 | 196 | 197 | // Check for matching format, put the data in the buffer 198 | if (pDdeData->cfFormat == CF_TEXT) 199 | { 200 | /* get the data back into RD */ 201 | if (rdpGRD) memcpy(rdpGRD, (DDERAYDATA *) pDdeData->Value, (ngNumRays+1)*sizeof(DDERAYDATA)); 202 | else strcpy(szGlobalBuffer, (char *) pDdeData->Value); 203 | } 204 | 205 | GotData = 1; 206 | GlobalDeleteAtom (aItem); 207 | 208 | // Acknowledge if necessary 209 | if (pDdeData->fAckReq == TRUE) 210 | { 211 | wStatus = *((WORD *) &DdeAck); 212 | if (!PostMessage ((HWND) wParam, WM_DDE_ACK, (WPARAM) hwnd, PackDDElParam (WM_DDE_ACK, wStatus, aItem))) 213 | { 214 | if (hDdeData) 215 | { 216 | GlobalUnlock (hDdeData); 217 | GlobalFree (hDdeData); 218 | } 219 | return 0; 220 | } 221 | } 222 | 223 | // Clean up 224 | GlobalUnlock (hDdeData); 225 | if (pDdeData->fRelease == TRUE || DdeAck.fAck == FALSE) GlobalFree (hDdeData); 226 | return 0; 227 | 228 | case WM_DDE_ACK: 229 | /* we are receiving an acknowledgement */ 230 | /* the only one we care about is in response to the WM_DDE_INITIATE; otherwise free just the memory */ 231 | if (hwndServer == NULL) 232 | { 233 | uiLow = (UINT) NULL; 234 | uiHi = (UINT) NULL; 235 | UnpackDDElParam(WM_DDE_ACK, lParam, &uiLow, &uiHi); 236 | FreeDDElParam(WM_DDE_ACK, lParam); 237 | hwndServer = (HWND) wParam; 238 | if (uiLow) GlobalDeleteAtom((ATOM) uiLow); 239 | if (uiHi) GlobalDeleteAtom((ATOM) uiHi); 240 | } 241 | else 242 | { 243 | HWND dummy; 244 | uiLow = (UINT) NULL; 245 | uiHi = (UINT) NULL; 246 | UnpackDDElParam(WM_DDE_ACK, lParam, &uiLow, &uiHi); 247 | FreeDDElParam(WM_DDE_ACK, lParam); 248 | dummy = (HWND) wParam; 249 | if (uiLow) GlobalDeleteAtom((ATOM) uiLow); 250 | if (uiHi) GlobalDeleteAtom((ATOM) uiHi); 251 | } 252 | return 0; 253 | 254 | case WM_DDE_TERMINATE : 255 | PostMessage(hwndServer, WM_DDE_TERMINATE, (WPARAM) hwnd, 0L); 256 | hwndServer = NULL; 257 | return 0; 258 | 259 | case WM_PAINT : 260 | { 261 | PAINTSTRUCT ps; 262 | BeginPaint(hwnd, &ps); 263 | EndPaint(hwnd, &ps); 264 | } 265 | return 0; 266 | 267 | case WM_CLOSE : 268 | PostMessage(hwndServer, WM_DDE_TERMINATE, (WPARAM) hwnd, 0L); 269 | break; // for default processing 270 | 271 | case WM_DESTROY : 272 | PostQuitMessage(0); 273 | return 0; 274 | } 275 | return DefWindowProc(hwnd, iMsg, wParam, lParam); 276 | } 277 | 278 | void WaitForData(HWND hwnd) 279 | { 280 | int sleep_count; 281 | MSG msg; 282 | DWORD dwTime; 283 | dwTime = GetCurrentTime(); 284 | 285 | sleep_count = 0; 286 | 287 | while (!GotData) 288 | { 289 | while (PeekMessage (&msg, hwnd, WM_DDE_FIRST, WM_DDE_LAST, PM_REMOVE)) 290 | { 291 | DispatchMessage (&msg); 292 | } 293 | /* Give the server a chance to respond */ 294 | Sleep(0); 295 | sleep_count++; 296 | if (sleep_count > 10000) 297 | { 298 | if (GetCurrentTime() - dwTime > DDE_TIMEOUT) return; 299 | sleep_count = 0; 300 | } 301 | } 302 | } 303 | 304 | char * GetString(char *szBuffer, int n, char *szSubString) 305 | { 306 | int i, j, k; 307 | char szTest[5000]; 308 | 309 | szSubString[0] = '\0'; 310 | i = 0; 311 | j = 0; 312 | k = 0; 313 | while (szBuffer[i] && (k <= n) ) 314 | { 315 | szTest[j] = szBuffer[i]; 316 | 317 | if (szBuffer[i] == '"') 318 | { 319 | 320 | i++; 321 | j++; 322 | szTest[j] = szBuffer[i]; 323 | 324 | /* we have a double quote; keep reading until EOF or another double quote */ 325 | while(szBuffer[i] != '"' && szBuffer[i]) 326 | { 327 | i++; 328 | j++; 329 | szTest[j] = szBuffer[i]; 330 | } 331 | } 332 | 333 | if (szTest[j] == ' ' || szTest[j] == '\n' || szTest[j] == '\r' || szTest[j] == '\0' || szTest[j] == ',') 334 | { 335 | szTest[j] = '\0'; 336 | if (k == n) 337 | { 338 | strcpy(szSubString, szTest); 339 | return szSubString; 340 | } 341 | k++; 342 | j = -1; 343 | } 344 | i++; 345 | j++; 346 | } 347 | 348 | szTest[j] = '\0'; 349 | if (k == n) strcpy(szSubString, szTest); 350 | 351 | return szSubString; 352 | } 353 | 354 | int PostRequestMessage(char *szItem, char *szBuffer) 355 | { 356 | ATOM aItem; 357 | 358 | aItem = GlobalAddAtom(szItem); 359 | 360 | /* clear the buffers */ 361 | szGlobalBuffer[0] = '\0'; 362 | szBuffer[0] = '\0'; 363 | 364 | GotData = 0; 365 | 366 | if (!PostMessage(hwndServer, WM_DDE_REQUEST, (WPARAM) hwndClient, PackDDElParam(WM_DDE_REQUEST, CF_TEXT, aItem))) 367 | { 368 | MessageBox (hwndClient, "Cannot communicate with ZEMAX!", "Hello?", MB_ICONEXCLAMATION | MB_OK); 369 | GlobalDeleteAtom(aItem); 370 | return -1; 371 | } 372 | 373 | WaitForData(hwndClient); 374 | strcpy(szBuffer, szGlobalBuffer); 375 | 376 | if (GotData) return 0; 377 | else return -1; 378 | } 379 | 380 | int PostArrayTraceMessage(char *szBuffer, DDERAYDATA *RD) 381 | { 382 | ATOM aItem; 383 | HGLOBAL hPokeData; 384 | DDEPOKE * lpPokeData; 385 | long numbytes; 386 | int numrays; 387 | 388 | 389 | if (RD[0].opd > 4) 390 | { 391 | /* NSC Rays */ 392 | numrays = (int)RD[0].opd - 5; 393 | } 394 | else 395 | { 396 | /* sequential rays */ 397 | numrays = RD[0].error; 398 | } 399 | 400 | /* point to where the data is */ 401 | rdpGRD = RD; 402 | ngNumRays = numrays; 403 | 404 | numbytes = (1+numrays)*sizeof(DDERAYDATA); 405 | hPokeData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, (LONG) sizeof(DDEPOKE) + numbytes); 406 | lpPokeData = (DDEPOKE *) GlobalLock(hPokeData); 407 | lpPokeData->fRelease = TRUE; 408 | lpPokeData->cfFormat = CF_TEXT; 409 | memcpy(lpPokeData->Value, RD, numbytes); 410 | 411 | /* clear the buffers */ 412 | szGlobalBuffer[0] = '\0'; 413 | szBuffer[0] = '\0'; 414 | 415 | aItem = GlobalAddAtom("RayArrayData"); 416 | GlobalUnlock(hPokeData); 417 | 418 | GotData = 0; 419 | 420 | if (!PostMessage(hwndServer, WM_DDE_POKE, (WPARAM) hwndClient, PackDDElParam(WM_DDE_POKE, (UINT) hPokeData, aItem))) 421 | { 422 | MessageBox (hwndClient, "Cannot communicate with ZEMAX!", "Hello?", MB_ICONEXCLAMATION | MB_OK); 423 | GlobalDeleteAtom(aItem); 424 | GlobalFree(hPokeData); 425 | return -1; 426 | } 427 | GlobalDeleteAtom(aItem); 428 | 429 | WaitForData(hwndClient); 430 | 431 | strcpy(szBuffer, szGlobalBuffer); 432 | 433 | /* clear the pointer */ 434 | rdpGRD = NULL; 435 | 436 | if (GotData) return 0; 437 | else return -1; 438 | } 439 | 440 | void MakeEmptyWindow(int text, char *szAppName, char *szOptions) 441 | { 442 | char szOutputFile[260], szModuleName[260], szBuffer[5000]; 443 | FILE *output; 444 | 445 | /* get the output file name */ 446 | GetString(szCommandLine, 2, szOutputFile); 447 | 448 | /* get the module name */ 449 | GetModuleFileName(globalhInstance, szModuleName, 255); 450 | 451 | if ((output = fopen(szOutputFile, "wt")) == NULL) 452 | { 453 | /* can't open the file!! */ 454 | return; 455 | } 456 | 457 | if (text) 458 | { 459 | fputs("System is invalid, cannot compute data.\n",output); 460 | fclose(output); 461 | /* create a text window. Note we pass back the filename, module name, and activesurf as a single setting parameter. */ 462 | sprintf(szBuffer,"MakeTextWindow,\"%s\",\"%s\",\"%s\",%s", szOutputFile, szModuleName, szAppName, szOptions); 463 | PostRequestMessage(szBuffer, szBuffer); 464 | } 465 | else 466 | { 467 | fputs("NOFRAME\n",output); 468 | fputs("TEXT \"System is invalid, cannot compute data.\" .1 .5\n",output); 469 | fclose(output); 470 | sprintf(szBuffer,"MakeGraphicWindow,\"%s\",\"%s\",\"%s\",1,%s", szOutputFile, szModuleName, szAppName, szOptions); 471 | PostRequestMessage(szBuffer, szBuffer); 472 | } 473 | } 474 | 475 | void CenterWindow(HWND hwnd) 476 | { 477 | RECT rect; 478 | int newx, newy; 479 | GetWindowRect(hwnd, &rect); 480 | newx = (GetSystemMetrics(SM_CXSCREEN) - (rect.right - rect.left))/2; 481 | newy = (GetSystemMetrics(SM_CYSCREEN) - (rect.bottom - rect.top))/2; 482 | SetWindowPos(hwnd, HWND_TOP, newx, newy, 0, 0, SWP_NOSIZE); 483 | } 484 | 485 | void Get_2_5_10(double cmax, double *cscale) 486 | { 487 | int i; 488 | double temp; 489 | if (cmax <= 0) 490 | { 491 | *cscale = .00001; 492 | return; 493 | } 494 | *cscale = log10(cmax); 495 | i = 0; 496 | for (; *cscale < 0; i--) *cscale = *cscale + 1; 497 | for (; *cscale > 1; i++) *cscale = *cscale - 1; 498 | temp = 10; 499 | if (*cscale < log10(5.0)) temp = 5; 500 | if (*cscale < log10(2.0)) temp = 2; 501 | *cscale = temp * pow(10, (double) i ); 502 | } 503 | 504 | void remove_quotes(char *s) 505 | { 506 | int i=0; 507 | /* remove the first quote if it exists */ 508 | if (s[0] == '"') 509 | { 510 | while (s[i]) 511 | { 512 | s[i] = s[i+1]; 513 | i++; 514 | } 515 | } 516 | /* remove the last quote if it exists */ 517 | if (strlen(s) > 0) 518 | { 519 | if (s[strlen(s)-1] == '"') s[strlen(s)-1] = '\0'; 520 | } 521 | } 522 | -------------------------------------------------------------------------------- /AHK/license.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | 283 | ------------ 284 | PCRE LICENCE 285 | ------------ 286 | 287 | PCRE is a library of functions to support regular expressions whose syntax 288 | and semantics are as close as possible to those of the Perl 5 language. 289 | 290 | Release 6 of PCRE is distributed under the terms of the "BSD" licence, as 291 | specified below. The documentation for PCRE, supplied in the "doc" 292 | directory, is distributed under the same terms as the software itself. 293 | 294 | The basic library functions are written in C and are freestanding. Also 295 | included in the distribution is a set of C++ wrapper functions. 296 | 297 | 298 | THE BASIC LIBRARY FUNCTIONS 299 | --------------------------- 300 | 301 | Written by: Philip Hazel 302 | Email local part: ph10 303 | Email domain: cam.ac.uk 304 | 305 | University of Cambridge Computing Service, 306 | Cambridge, England. Phone: +44 1223 334714. 307 | 308 | Copyright (c) 1997-2006 University of Cambridge 309 | All rights reserved. 310 | 311 | 312 | THE C++ WRAPPER FUNCTIONS 313 | ------------------------- 314 | 315 | Contributed by: Google Inc. 316 | 317 | Copyright (c) 2006, Google Inc. 318 | All rights reserved. 319 | 320 | 321 | THE "BSD" LICENCE 322 | ----------------- 323 | 324 | Redistribution and use in source and binary forms, with or without 325 | modification, are permitted provided that the following conditions are met: 326 | 327 | * Redistributions of source code must retain the above copyright notice, 328 | this list of conditions and the following disclaimer. 329 | 330 | * Redistributions in binary form must reproduce the above copyright 331 | notice, this list of conditions and the following disclaimer in the 332 | documentation and/or other materials provided with the distribution. 333 | 334 | * Neither the name of the University of Cambridge nor the name of Google 335 | Inc. nor the names of their contributors may be used to endorse or 336 | promote products derived from this software without specific prior 337 | written permission. 338 | 339 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 340 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 341 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 342 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 343 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 344 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 345 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 346 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 347 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 348 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 349 | POSSIBILITY OF SUCH DAMAGE. 350 | 351 | End 352 | -------------------------------------------------------------------------------- /pyzdde/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 | # PyZDDE 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 | def RequestArrayTrace(self, ddeRayData, timeout=None): 225 | """Request bulk ray tracing 226 | 227 | Parameters 228 | ---------- 229 | ddeRayData : the ray data for array trace 230 | """ 231 | pass 232 | # TO DO!!! 233 | # 1. Assign proper timeout as in Request() function 234 | # 2. Create the rayData structure conforming to ctypes structure 235 | # 3. Process the reply and return ray trace data 236 | # 4. Handle errors 237 | #reply = self.ddec.poke("RayArrayData", rayData, timeout) 238 | 239 | def SetDDETimeout(self, timeout): 240 | """Set DDE timeout 241 | timeout : timeout in seconds 242 | """ 243 | self.ddetimeout = timeout 244 | 245 | def GetDDETimeout(self): 246 | """Returns the current timeout value in seconds 247 | """ 248 | return self.ddetimeout 249 | 250 | 251 | def get_winfunc(libname, funcname, restype=None, argtypes=(), _libcache={}): 252 | """Retrieve a function from a library/DLL, and set the data types.""" 253 | if libname not in _libcache: 254 | _libcache[libname] = windll.LoadLibrary(libname) 255 | func = getattr(_libcache[libname], funcname) 256 | func.argtypes = argtypes 257 | func.restype = restype 258 | return func 259 | 260 | class DDE(object): 261 | """Object containing all the DDEML functions""" 262 | AccessData = get_winfunc("user32", "DdeAccessData", LPBYTE, (HDDEDATA, LPDWORD)) 263 | ClientTransaction = get_winfunc("user32", "DdeClientTransaction", HDDEDATA, (LPBYTE, DWORD, HCONV, HSZ, UINT, UINT, DWORD, LPDWORD)) 264 | Connect = get_winfunc("user32", "DdeConnect", HCONV, (DWORD, HSZ, HSZ, PCONVCONTEXT)) 265 | CreateDataHandle = get_winfunc("user32", "DdeCreateDataHandle", HDDEDATA, (DWORD, LPBYTE, DWORD, DWORD, HSZ, UINT, UINT)) 266 | CreateStringHandle = get_winfunc("user32", "DdeCreateStringHandleW", HSZ, (DWORD, LPCWSTR, UINT)) # Unicode version 267 | #CreateStringHandle = get_winfunc("user32", "DdeCreateStringHandleA", HSZ, (DWORD, LPCSTR, UINT)) # ANSI version 268 | Disconnect = get_winfunc("user32", "DdeDisconnect", BOOL, (HCONV,)) 269 | GetLastError = get_winfunc("user32", "DdeGetLastError", UINT, (DWORD,)) 270 | Initialize = get_winfunc("user32", "DdeInitializeW", UINT, (LPDWORD, DDECALLBACK, DWORD, DWORD)) # Unicode version of DDE initialize 271 | #Initialize = get_winfunc("user32", "DdeInitializeA", UINT, (LPDWORD, DDECALLBACK, DWORD, DWORD)) # ANSI version of DDE initialize 272 | FreeDataHandle = get_winfunc("user32", "DdeFreeDataHandle", BOOL, (HDDEDATA,)) 273 | FreeStringHandle = get_winfunc("user32", "DdeFreeStringHandle", BOOL, (DWORD, HSZ)) 274 | QueryString = get_winfunc("user32", "DdeQueryStringA", DWORD, (DWORD, HSZ, LPSTR, DWORD, c_int)) # ANSI version of QueryString 275 | UnaccessData = get_winfunc("user32", "DdeUnaccessData", BOOL, (HDDEDATA,)) 276 | Uninitialize = get_winfunc("user32", "DdeUninitialize", BOOL, (DWORD,)) 277 | 278 | class DDEError(RuntimeError): 279 | """Exception raise when a DDE error occures.""" 280 | def __init__(self, msg, idInst=None): 281 | if idInst is None: 282 | RuntimeError.__init__(self, msg) 283 | else: 284 | RuntimeError.__init__(self, "%s (err=%s)" % (msg, hex(DDE.GetLastError(idInst)))) 285 | 286 | class DDEClient(object): 287 | """The DDEClient class. 288 | 289 | Use this class to create and manage a connection to a service/topic. To get 290 | classbacks subclass DDEClient and overwrite callback.""" 291 | 292 | def __init__(self, service, topic): 293 | """Create a connection to a service/topic.""" 294 | self._idInst = DWORD(0) # application instance identifier. 295 | self._hConv = HCONV() 296 | 297 | self._callback = DDECALLBACK(self._callback) 298 | # Initialize and register application with DDEML 299 | res = DDE.Initialize(byref(self._idInst), self._callback, APPCMD_CLIENTONLY, 0) 300 | if res != DMLERR_NO_ERROR: 301 | raise DDEError("Unable to register with DDEML (err=%s)" % hex(res)) 302 | 303 | hszServName = DDE.CreateStringHandle(self._idInst, service, CP_WINUNICODE) 304 | hszTopic = DDE.CreateStringHandle(self._idInst, topic, CP_WINUNICODE) 305 | # Try to establish conversation with the Zemax server 306 | self._hConv = DDE.Connect(self._idInst, hszServName, hszTopic, PCONVCONTEXT()) 307 | DDE.FreeStringHandle(self._idInst, hszTopic) 308 | DDE.FreeStringHandle(self._idInst, hszServName) 309 | if not self._hConv: 310 | raise DDEError("Unable to establish a conversation with server", self._idInst) 311 | 312 | def __del__(self): 313 | """Cleanup any active connections and free all DDEML resources.""" 314 | if self._hConv: 315 | DDE.Disconnect(self._hConv) 316 | if self._idInst: 317 | DDE.Uninitialize(self._idInst) 318 | 319 | def advise(self, item, stop=False): 320 | """Request updates when DDE data changes.""" 321 | hszItem = DDE.CreateStringHandle(self._idInst, item, CP_WINUNICODE) 322 | hDdeData = DDE.ClientTransaction(LPBYTE(), 0, self._hConv, hszItem, CF_TEXT, XTYP_ADVSTOP if stop else XTYP_ADVSTART, TIMEOUT_ASYNC, LPDWORD()) 323 | DDE.FreeStringHandle(self._idInst, hszItem) 324 | if not hDdeData: 325 | raise DDEError("Unable to %s advise" % ("stop" if stop else "start"), self._idInst) 326 | DDE.FreeDataHandle(hDdeData) 327 | 328 | def execute(self, command): 329 | """Execute a DDE command.""" 330 | pData = c_char_p(command) 331 | cbData = DWORD(len(command) + 1) 332 | hDdeData = DDE.ClientTransaction(pData, cbData, self._hConv, HSZ(), CF_TEXT, XTYP_EXECUTE, TIMEOUT_ASYNC, LPDWORD()) 333 | if not hDdeData: 334 | raise DDEError("Unable to send command", self._idInst) 335 | DDE.FreeDataHandle(hDdeData) 336 | 337 | def request(self, item, timeout=5000): 338 | """Request data from DDE service.""" 339 | hszItem = DDE.CreateStringHandle(self._idInst, item, CP_WINUNICODE) 340 | #hDdeData = DDE.ClientTransaction(LPBYTE(), 0, self._hConv, hszItem, CF_TEXT, XTYP_REQUEST, timeout, LPDWORD()) 341 | pdwResult = DWORD(0) 342 | hDdeData = DDE.ClientTransaction(LPBYTE(), 0, self._hConv, hszItem, CF_TEXT, XTYP_REQUEST, timeout, byref(pdwResult)) 343 | DDE.FreeStringHandle(self._idInst, hszItem) 344 | if not hDdeData: 345 | raise DDEError("Unable to request item", self._idInst) 346 | 347 | if timeout != TIMEOUT_ASYNC: 348 | pdwSize = DWORD(0) 349 | pData = DDE.AccessData(hDdeData, byref(pdwSize)) 350 | if not pData: 351 | DDE.FreeDataHandle(hDdeData) 352 | raise DDEError("Unable to access data in request function", self._idInst) 353 | DDE.UnaccessData(hDdeData) 354 | else: 355 | pData = None 356 | DDE.FreeDataHandle(hDdeData) 357 | return pData 358 | 359 | def poke(self, item, data, timeout=5000): 360 | """Poke (unsolicited) data to DDE server""" 361 | hszItem = DDE.CreateStringHandle(self._idInst, item, CP_WINUNICODE) 362 | pData = c_char_p(data) 363 | cbData = DWORD(len(data) + 1) 364 | pdwResult = DWORD(0) 365 | #hData = DDE.CreateDataHandle(self._idInst, data, cbData, 0, hszItem, CP_WINUNICODE, 0) 366 | #hDdeData = DDE.ClientTransaction(hData, -1, self._hConv, hszItem, CF_TEXT, XTYP_POKE, timeout, LPDWORD()) 367 | hDdeData = DDE.ClientTransaction(pData, cbData, self._hConv, hszItem, CF_TEXT, XTYP_POKE, timeout, byref(pdwResult)) 368 | DDE.FreeStringHandle(self._idInst, hszItem) 369 | #DDE.FreeDataHandle(dData) 370 | if not hDdeData: 371 | print("Value of pdwResult: ", pdwResult) 372 | raise DDEError("Unable to poke to server", self._idInst) 373 | 374 | if timeout != TIMEOUT_ASYNC: 375 | pdwSize = DWORD(0) 376 | pData = DDE.AccessData(hDdeData, byref(pdwSize)) 377 | if not pData: 378 | DDE.FreeDataHandle(hDdeData) 379 | raise DDEError("Unable to access data in poke function", self._idInst) 380 | # TODO: use pdwSize 381 | DDE.UnaccessData(hDdeData) 382 | else: 383 | pData = None 384 | DDE.FreeDataHandle(hDdeData) 385 | return pData 386 | 387 | def callback(self, value, item=None): 388 | """Callback function for advice.""" 389 | print("callback: %s: %s" % (item, value)) 390 | 391 | def _callback(self, wType, uFmt, hConv, hsz1, hsz2, hDdeData, dwData1, dwData2): 392 | """DdeCallback callback function for processing Dynamic Data Exchange (DDE) 393 | transactions sent by DDEML in response to DDE events 394 | 395 | Parameters 396 | ---------- 397 | wType : transaction type (UINT) 398 | uFmt : clipboard data format (UINT) 399 | hConv : handle to conversation (HCONV) 400 | hsz1 : handle to string (HSZ) 401 | hsz2 : handle to string (HSZ) 402 | hDDedata : handle to global memory object (HDDEDATA) 403 | dwData1 : transaction-specific data (DWORD) 404 | dwData2 : transaction-specific data (DWORD) 405 | 406 | Returns 407 | ------- 408 | ret : specific to the type of transaction (HDDEDATA) 409 | """ 410 | if wType == XTYP_ADVDATA: # value of the data item has changed [hsz1 = topic; hsz2 = item; hDdeData = data] 411 | dwSize = DWORD(0) 412 | pData = DDE.AccessData(hDdeData, byref(dwSize)) 413 | if pData: 414 | item = create_string_buffer('\000' * 128) 415 | DDE.QueryString(self._idInst, hsz2, item, 128, CP_WINANSI) 416 | self.callback(pData, item.value) 417 | DDE.UnaccessData(hDdeData) 418 | return DDE_FACK 419 | else: 420 | print("Error: AccessData returned NULL! (err = %s)"% (hex(DDE.GetLastError(self._idInst)))) 421 | if wType == XTYP_DISCONNECT: 422 | print("Disconnect notification received from server") 423 | 424 | return 0 425 | 426 | def WinMSGLoop(): 427 | """Run the main windows message loop.""" 428 | LPMSG = POINTER(MSG) 429 | LRESULT = c_ulong 430 | GetMessage = get_winfunc("user32", "GetMessageW", BOOL, (LPMSG, HWND, UINT, UINT)) 431 | TranslateMessage = get_winfunc("user32", "TranslateMessage", BOOL, (LPMSG,)) 432 | # restype = LRESULT 433 | DispatchMessage = get_winfunc("user32", "DispatchMessageW", LRESULT, (LPMSG,)) 434 | 435 | msg = MSG() 436 | lpmsg = byref(msg) 437 | while GetMessage(lpmsg, HWND(), 0, 0) > 0: 438 | TranslateMessage(lpmsg) 439 | DispatchMessage(lpmsg) 440 | 441 | if __name__ == "__main__": 442 | pass 443 | 444 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os, sys, time, datetime 4 | import subprocess 5 | import pyzdde.zdde as pyz 6 | import configparser 7 | from distutils.dir_util import copy_tree 8 | from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog, QMessageBox, QListWidgetItem, QInputDialog 9 | from PyQt5 import uic 10 | import glob 11 | import psutil 12 | import platform 13 | 14 | import pandas as pd 15 | 16 | Ui_MainWindow, QtBaseClass = uic.loadUiType("gui.ui") 17 | 18 | class MyApp(QMainWindow): 19 | 20 | def __init__(self): 21 | super(MyApp, self).__init__() 22 | 23 | '''Registrierung der Buttons''' 24 | self.ui = Ui_MainWindow() 25 | self.ui.setupUi(self) 26 | self.ui.OpenZemaxButton.clicked.connect(self.OpenZemax) 27 | self.ui.SettingsMenu.clicked.connect(self.openSettings) 28 | self.ui.createSubfolders.clicked.connect(self.createSubfolders) 29 | self.ui.save_n_transfer.clicked.connect(self.save_n_transfer) 30 | self.ui.ListDir.clicked.connect(self.ListDir) 31 | self.ui.LoadSystemFrom.clicked.connect(self.LoadSystemFrom) 32 | self.ui.LoadSC.clicked.connect(self.LoadSC) 33 | self.ui.LoadNSC.clicked.connect(self.LoadNSC) 34 | self.ui.EditComment.clicked.connect(self.EditComment) 35 | self.ui.SaveModel.clicked.connect(self.Archive) 36 | self.ui.ConvertCad.clicked.connect(self.ConvertCad) 37 | self.ui.Help.clicked.connect(self.Help) 38 | #self.ui.Merit.clicked.connect(self.ManualOptimize) 39 | self.ui.Merit_auto.clicked.connect(self.AutoOptimize) 40 | self.ui.trace.clicked.connect(self.ray_trace) 41 | self.ui.save_detect.clicked.connect(self.save_detector_file) 42 | self.ui.delete_2.clicked.connect(self.delete_entries) 43 | self.getZemaxExec() 44 | self.showWorkingDir() 45 | 46 | self.ListDir() 47 | 48 | def delete_entries(self): 49 | print('deletion started') 50 | selected_system = self.ui.listWidget.currentItem().text() 51 | splitted = str.split(selected_system) 52 | chosenArchive = splitted[0] 53 | print('Archive: ' + chosenArchive) 54 | 55 | #Empty live folder 56 | files = glob.glob(self.configDir + '/archiv/' + chosenArchive + '/*') 57 | for f in files: 58 | os.remove(f) 59 | print('Files removed') 60 | 61 | #Remove Archiv folder 62 | os.rmdir(self.configDir + '/archiv/' + chosenArchive) 63 | print('Folder removed') 64 | 65 | df = pd.read_csv(self.configDir + '/archiv/comments.csv', sep=';') 66 | 67 | print(df) 68 | df = df[df['a'] != int(chosenArchive)] 69 | 70 | 71 | '''Save to csv an refresh view''' 72 | df.to_csv(self.configDir + '/archiv/comments.csv', sep=';', index=False) 73 | self.ListDir() 74 | 75 | 76 | def ray_trace(self): 77 | print('start ray tracing') 78 | subprocess.call(["AHK\AutoHotkeyA32.exe", 79 | "ray_trace.ahk"]) 80 | 81 | 82 | def read_detector_power(self): 83 | 84 | #Read total Power from detector file 85 | 86 | file = open(self.configDir + '/live/' + 'detectorViewerFile.txt','r') 87 | dirty_string = file.read(1500) 88 | clean_list = dirty_string.replace('\x00', '').split('\n') 89 | total_power_string = clean_list[22] 90 | print(total_power_string) 91 | total_power_string_splitted = total_power_string.split(' ') 92 | watt_string = total_power_string_splitted[7] 93 | self.watt = float(watt_string.replace(',','.')) 94 | print('Power: ' + str(self.watt)) 95 | #Push power to gui 96 | 97 | self.ui.power_last_run.setText('Power last run: ' + str(self.watt)) 98 | 99 | def save_detector_file(self): 100 | print('save detector file') 101 | #This function is a bit broken. It throws an error but still does it's job 102 | try: 103 | self.link = pyz.createLink() 104 | self.link.zGetDetectorViewer() 105 | self.link.close() 106 | except: 107 | self.link.close() 108 | time.sleep(4) 109 | print('Saving detectorFile finished') 110 | 111 | self.read_detector_power() 112 | 113 | 114 | 115 | 116 | 117 | #Warining! Function should only be called one time per replacement_name. 118 | # =>Double replacement 119 | def set_ray_file(self, ray_file_name, replacement_name): 120 | self.link = pyz.createLink() 121 | 122 | #Check if in SC-Mode 123 | if not self.link._zGetMode()[0] == 1: 124 | self.link.close() 125 | QMessageBox.about(self, "Title", "Function is only available in NSC-Mode.") 126 | return None 127 | 128 | for i in range(20): 129 | comment = self.link.zGetNSCProperty(1,i,1,1) 130 | print(comment) 131 | if comment == replacement_name: 132 | print('replacement position found') 133 | z_position = self.link.zGetNSCPosition(1,i)[2] 134 | self.link.zSetNSCProperty(1,i,1,0, value='NSC_SFIL') 135 | print('type replaced') 136 | print('ray_file_name: ' + str(type(ray_file_name))) 137 | self.link.zSetNSCProperty(1,i,1,1, value=str(ray_file_name)) 138 | self.link.zSetNSCPosition(1,i,3,z_position) 139 | self.link.zPushLens() 140 | break 141 | self.link.close() 142 | 143 | 144 | def set_ray_files(self): 145 | self.config = configparser.ConfigParser() 146 | self.config.read('config.ini') 147 | 148 | checkbox_one = self.config.get('RAYFIELD', 'one_checked', fallback='') 149 | field_one = self.config.get('RAYFIELD', 'one', fallback='') 150 | if checkbox_one == 'True': 151 | self.set_ray_file(ray_file_name=field_one, replacement_name='Field 1') 152 | 153 | checkbox_two = self.config.get('RAYFIELD', 'two_checked', fallback='') 154 | field_two = self.config.get('RAYFIELD', 'two', fallback='') 155 | if checkbox_two == 'True': 156 | self.set_ray_file(ray_file_name=field_two, replacement_name='Field 2') 157 | 158 | checkbox_three = self.config.get('RAYFIELD', 'three_checked', fallback='') 159 | field_three = self.config.get('RAYFIELD', 'three', fallback='') 160 | if checkbox_three == 'True': 161 | self.set_ray_file(ray_file_name=field_three, replacement_name='Field 3') 162 | 163 | checkbox_four = self.config.get('RAYFIELD', 'four_checked', fallback='') 164 | field_four = self.config.get('RAYFIELD', 'four', fallback='') 165 | if checkbox_four == 'True': 166 | self.set_ray_file(ray_file_name=field_four, replacement_name='Field 4') 167 | 168 | 169 | def AutoOptimize(self): 170 | print('Auto optimize started') 171 | self.read_detector_power() 172 | self.ManualOptimize(auto_value = self.watt) 173 | 174 | 175 | def ManualOptimize(self, auto_value = 'False'): 176 | 177 | '''Zemax Verbindung aufbauen''' 178 | self.link = pyz.createLink() 179 | 180 | #Check if in SC-Mode 181 | if not self.link._zGetMode()[0] == 0: 182 | self.link.close() 183 | QMessageBox.about(self, "Title", "Function is not available in NSC-Mode.") 184 | return None 185 | 186 | self.link.close() 187 | 188 | self.delete_old_merit() 189 | print('deleteMFO ended') 190 | 191 | self.link = pyz.createLink() 192 | 193 | '''Check Merit Function for Signal''' 194 | optimizer_row = '' 195 | for i in range(1,300): 196 | comment = self.link.zGetOperand(row=i, column=2) 197 | print(comment) 198 | if comment == 'OPTIMIZER': 199 | print('OPTIMIZER FOUND') 200 | print('row: ' + str(i)) 201 | optimizer_row = i 202 | break 203 | 204 | optimizer_row_end = '' 205 | for j in range(1,300): 206 | comment = self.link.zGetOperand(row=j, column=2) 207 | if comment == 'ENDOPTIMIZER': 208 | print('ENDOPTIMIZER FOUND') 209 | print('row_end: ' + str(j)) 210 | optimizer_row_end = j 211 | break 212 | 213 | if (optimizer_row == '') or (optimizer_row_end == ''): 214 | print('OPTIMIZER row not found') 215 | QMessageBox.about(self, "Error", "'OPTIMIZER' row not found.") 216 | return 217 | 218 | #Anzahl der Meritfunctions die betrachtet werden 219 | merits = 1 220 | for i in range(merits): 221 | self.link.zInsertMFO(optimizer_row+1) 222 | 223 | 224 | self.link.zSetOperand(row=(optimizer_row+1), column=1, value='ZPLM') 225 | 226 | prog = 17 #Macro has the name 'ZPL17.ZPL' 227 | self.link.zSetOperand(row=(optimizer_row+1), column=2, value=prog) 228 | 229 | data = 0 #Valueposition 0 is read from the macro 230 | self.link.zSetOperand(row=(optimizer_row+1), column=3, value=data) 231 | 232 | 233 | weight = str(self.ui.SpinBox_Weight.value()) 234 | self.link.zSetOperand(row=(optimizer_row+1), column=9, value=weight) 235 | 236 | Target = str(self.ui.SpinBox_Target.value()) 237 | self.link.zSetOperand(row=(optimizer_row+1), column=8, value=Target) 238 | 239 | print(str(type(auto_value))) 240 | # if auto_target == False: 241 | # print('auto_target empty') 242 | # Target = str(self.ui.SpinBox_Value.value()) 243 | # self.link.zSetOperand(row=(optimizer_row+1), column=4, value=Target) 244 | if str(type(auto_value)) == "" : 245 | print('auto_target: ' + str(auto_value)) 246 | Value = str(1/auto_value) 247 | self.link.zSetOperand(row=(optimizer_row+1), column=4, value=Value) 248 | 249 | self.link.zPushLens() 250 | self.link.close() 251 | 252 | subprocess.call(["AHK\AutoHotkeyA32.exe", 253 | "optimize.ahk"]) 254 | 255 | time.sleep (5) 256 | 257 | 258 | 259 | def delete_old_merit(self): 260 | '''Zemax Verbindung aufbauen''' 261 | self.link = pyz.createLink() 262 | 263 | print('called deleteMFO') 264 | optimizer_row = '' 265 | for i in range(1,300): 266 | comment = self.link.zGetOperand(row=i, column=2) 267 | if comment == 'OPTIMIZER': 268 | print('OPTIMIZER FOUND') 269 | print('row: ' + str(i)) 270 | optimizer_row = i 271 | break 272 | 273 | optimizer_row_end = '' 274 | for j in range(1,300): 275 | comment = self.link.zGetOperand(row=j, column=2) 276 | if comment == 'ENDOPTIMIZER': 277 | print('ENDOPTIMIZER FOUND') 278 | print('row_end: ' + str(j)) 279 | optimizer_row_end = j 280 | break 281 | self.link.close() 282 | 283 | if (optimizer_row == '') or (optimizer_row_end == ''): 284 | print('OPTIMIZER row not found') 285 | QMessageBox.about(self, "Error", "'OPTIMIZER' row not found.") 286 | return 287 | elif (optimizer_row_end - optimizer_row) >= 2: 288 | self.link = pyz.createLink() 289 | self.link.zDeleteMFO(optimizer_row +1 ) 290 | self.link.zPushLens() 291 | self.link.close() 292 | self.delete_old_merit() 293 | 294 | print('deleteMFO end') 295 | return 296 | 297 | def ConvertCad(self): 298 | 299 | '''Zemax Verbindung aufbauen''' 300 | self.link = pyz.createLink() 301 | 302 | #Check if in SC-Mode 303 | if not self.link._zGetMode()[0] == 1: 304 | self.link.close() 305 | QMessageBox.about(self, "Title", "Function is only available in NSC-Mode.") 306 | return None 307 | 308 | '''Save current NSC-Model''' 309 | time.sleep(0.5) 310 | success = self.link.zSaveFile(self.configDir + '/live' + '/lens-NSC.zmx') 311 | time.sleep(0.5) 312 | self.link.close() 313 | 314 | if success == 0: 315 | print('SUCCES-File saved') 316 | else: 317 | print('ERROR-File not saved') 318 | 319 | '''Execute userfunction. Saves Step-file in Live folder''' 320 | '''Win 7 and Win 10 have a different Save Dialoge. One more Tab is needed''' 321 | if platform.release() == '7': 322 | subprocess.call(["AHK\AutoHotkeyA32.exe", 323 | "Export_to_CAD_7.ahk", 324 | self.configDir + '/live/', 'lens-STEP.stp']) 325 | elif platform.release() == '10': 326 | subprocess.call(["AHK\AutoHotkeyA32.exe", 327 | "Export_to_CAD_10.ahk", 328 | self.configDir + '/live/', 'lens-STEP.stp']) 329 | else: 330 | QMessageBox.about(self, "Error", "OS not supported.") 331 | 332 | self.getInventorExec() 333 | subprocess.Popen([self.InventorPath, self.configDir + '/live' + '/lens-STEP.stp']) 334 | 335 | 336 | def EditComment(self): 337 | print('Comment edit') 338 | text, ok = QInputDialog.getText(self, 'Edit comment dialog', 'Enter comment:') 339 | 340 | if ok: 341 | self.saveComment(text=text, position='selection') 342 | 343 | def AddComment(self): 344 | print('Add comment') 345 | text, ok = QInputDialog.getText(self, 'Add comment dialog', 'Enter comment:') 346 | 347 | if ok: 348 | self.saveComment(text=text, position='end') 349 | 350 | def saveComment(self, text, position): 351 | 352 | '''Feststellen an welcher Stelle Kommentar geschrieben werden soll''' 353 | if position == 'selection': 354 | choosen_line = self.ui.listWidget.currentItem().text() 355 | def extract_num(string): 356 | splitted = str.split(string) 357 | return int(splitted[0]) 358 | choosen_system = extract_num(choosen_line) 359 | if position == 'end': 360 | choosen_system = self.getArchiveNumber() 361 | 362 | 363 | df = pd.read_csv(self.configDir + '/archiv/comments.csv', sep=';') 364 | #Wenn df nummer nicht enthält => Append 365 | if choosen_system not in df['a']: 366 | print('Comment nicht vorhanden') 367 | df = df.append(pd.Series([choosen_system, text], index=['a', 'b']), ignore_index=True) 368 | #Wenn df nummer enthält => change 369 | df.loc[df['a'] == choosen_system, ['b']] = text 370 | 371 | '''Save to csv an refresh view''' 372 | df.to_csv(self.configDir + '/archiv/comments.csv', sep=';', index=False) 373 | self.ListDir() 374 | 375 | def LoadSC(self): 376 | #Check if Zemax is running 377 | if self.isZemaxRunning(): 378 | QMessageBox.about(self, "Title", "Function is not available while ZEMAX is running.") 379 | return None 380 | subprocess.Popen([self.ZemaxPath, self.configDir + '/live' + '/lensfile.zmx']) 381 | 382 | def LoadNSC(self): 383 | #Check if Zemax is running 384 | if self.isZemaxRunning(): 385 | QMessageBox.about(self, "Title", "Function is not available while ZEMAX is running.") 386 | return None 387 | subprocess.Popen([self.ZemaxPath, 388 | self.configDir + '/live' + '/lens-NSC.zmx']) 389 | 390 | def LoadSystemFrom(self): 391 | #Check if Zemax is running 392 | if self.isZemaxRunning(): 393 | QMessageBox.about(self, "Title", "Function is not available while ZEMAX is running.") 394 | return None 395 | current_system = self.ui.listWidget.currentItem().text() 396 | splitted = str.split(current_system) 397 | chosenArchive = splitted[0] 398 | #Archive system in live folder 399 | self.Archive() 400 | 401 | #Empty live folder 402 | files = glob.glob(self.configDir + '/live' + '/*') 403 | for f in files: 404 | os.remove(f) 405 | 406 | #Copy from archive folder 407 | destDirectory = self.configDir + '/live' 408 | fromDirectory = self.configDir + '/archiv/' + str(chosenArchive) 409 | copy_tree(fromDirectory, destDirectory) 410 | 411 | self.ListDir() 412 | 413 | '''Lists all folders with mtime in the archive folder''' 414 | def ListDir(self): 415 | if os.path.isdir(self.configDir) == False: 416 | return 417 | if os.path.exists(self.configDir + '/archiv') == False: 418 | return 419 | '''Load comment file''' 420 | df = pd.read_csv(self.configDir + '/archiv/comments.csv', sep=';') 421 | 422 | self.ui.listWidget.clear() 423 | for i in os.listdir(os.path.join(self.configDir + '/archiv')): 424 | #If Objekt in archive folde is not an folder => skip (see comments.csv) 425 | if os.path.isdir(self.configDir + '/archiv/' + str(i)) == False: 426 | continue 427 | 428 | '''Filter comments from csv file''' 429 | comment_line = df.loc[df['a'] == int(i), 'b'] 430 | if comment_line.size == 0: 431 | comment = '' 432 | else: 433 | comment = comment_line.iloc[0] 434 | print(comment) 435 | 436 | #Extrahierung und Formatierung der mtime 437 | stat = os.stat(os.path.join(self.configDir + '/archiv/' + str(i))) 438 | date = datetime.datetime.fromtimestamp(stat.st_mtime) 439 | dt = '{0:%Y-%m-%d %H:%M:%S}'.format(date) 440 | 441 | '''Ein wenig hübscher formatiert''' 442 | if int(i) < 10: 443 | self.ui.listWidget.addItem(ListWidgetItem(' {:12}{:25}{}'.format(i, str(dt), str(comment)))) 444 | else: 445 | self.ui.listWidget.addItem(ListWidgetItem('{:12}{:25}{}'.format(i, str(dt), str(comment)))) 446 | self.showWorkingDir() 447 | 448 | def getArchiveNumber(self): 449 | ''''Scans for int foldernames and returns highest number''' 450 | folders = filter(lambda x: os.path.isdir(os.path.join((self.configDir + '/archiv'), x)), 451 | os.listdir((self.configDir + '/archiv'))) 452 | number = 0 453 | for i in folders: 454 | if i.isdigit(): 455 | if int(i) > number: 456 | number = int(i) 457 | return number 458 | 459 | def save_n_transfer(self): 460 | #Save File 461 | '''Zemax Verbindung aufbauen''' 462 | self.link = pyz.createLink() 463 | 464 | #Check if in SC-Mode 465 | if not self.link._zGetMode()[0] == 0: 466 | self.link.close() 467 | QMessageBox.about(self, "Title", "Function is not available in NSC-Mode.") 468 | return None 469 | 470 | time.sleep(1) 471 | print('saving started') 472 | success = self.link.zSaveFile(self.configDir + '/live' + '/lensfile.zmx') 473 | print('saving finished') 474 | time.sleep(1) 475 | 476 | self.link.close() 477 | if success == 0: 478 | print('SUCCES-File saved') 479 | else: 480 | print('ERROR-File not saved') 481 | self.Archive() 482 | #Transfer System to NSC-Group 483 | '''It is not supported by pyzdde to Convert from SC to NSC by Python-API''' 484 | '''The solution is to replace the user by a script.''' 485 | subprocess.call(["AHK\AutoHotkeyA32.exe", 486 | "Convert_to_NSGroup.ahk", 487 | self.configDir + '/live'+ '/lens-NSC.zmx']) 488 | time.sleep(10) 489 | 490 | self.set_ray_files() 491 | 492 | 493 | def Archive(self): 494 | #Copy to archiv 495 | #Create new archiv folder 496 | archiv_number = 1 + self.getArchiveNumber() 497 | os.mkdir(self.configDir + '/archiv/' + str(archiv_number)) 498 | 499 | #Copy to archive folder 500 | fromDirectory = self.configDir + '/live' 501 | destDirectory = self.configDir + '/archiv/' + str(archiv_number) 502 | copy_tree(fromDirectory, destDirectory) 503 | 504 | self.AddComment() 505 | 506 | 507 | 508 | def createSubfolders(self): 509 | print('Subfolder creation started') 510 | self.showWorkingDir() 511 | live = os.path.isdir(self.configDir + '/live') 512 | archiv = os.path.isdir(self.configDir + '/archiv') 513 | comments = os.path.isfile(self.configDir + '/archiv/comments.csv') 514 | if not live: 515 | print('no "live"') 516 | os.mkdir(self.configDir + '/live') 517 | if not archiv: 518 | print('no "archiv"') 519 | os.mkdir(self.configDir + '/archiv') 520 | if not comments: 521 | print('no comments file') 522 | df = pd.DataFrame({'a':[], 'b':[]}) 523 | df.to_csv(self.configDir + '/archiv/comments.csv', sep=';', index=False) 524 | 525 | def OpenZemax(self): 526 | print(self.ZemaxPath) 527 | subprocess.Popen([self.ZemaxPath]) 528 | 529 | def showWorkingDir(self): 530 | self.ui.label.setText('Working Dir not set or invalid') 531 | self.config = configparser.ConfigParser() 532 | self.config.read("config.ini") 533 | print(self.config.sections()) 534 | self.configDir = self.config.get('DEFAULT', 'WorkingDir', fallback='Working Dir not set') 535 | if os.path.isdir(self.configDir) == False: 536 | self.ui.label.setText('Working Dir not set or invalid') 537 | return 538 | self.ui.label.setText(self.configDir) 539 | 540 | def isZemaxRunning(self): 541 | isRunning = "OpticStudio.exe" in (p.name() for p in psutil.process_iter()) 542 | return isRunning 543 | 544 | def getZemaxExec(self): 545 | self.ZemaxPath = configparser.ConfigParser() 546 | self.ZemaxPath.read("config.ini") 547 | self.ZemaxPath = self.ZemaxPath.get('ZEMAX', 'zemaxexec', fallback='C:/') 548 | 549 | def getInventorExec(self): 550 | self.InventorPath = configparser.ConfigParser() 551 | self.InventorPath.read("config.ini") 552 | self.InventorPath = self.InventorPath.get('INVENTOR', 'inventorexec', fallback='C:/') 553 | 554 | def openSettings(self): 555 | print('Settings are being opened') 556 | dialog = Settings(self) 557 | dialog.show() 558 | 559 | def Help(self): 560 | print('Help is being opened') 561 | dialog = Help(self) 562 | dialog.show() 563 | 564 | #Function override to sort entries in the GUI 565 | class ListWidgetItem(QListWidgetItem): 566 | def __lt__(self, other): 567 | 568 | def extract_num(string): 569 | splitted = str.split(string) 570 | return splitted[0] 571 | 572 | if (int(extract_num(self.text())) < int(extract_num(other.text()))): 573 | return True 574 | else: 575 | return False 576 | 577 | #Settings dialoge 578 | class Help(QMainWindow): 579 | def __init__(self, parent=None): 580 | super(Help, self).__init__(parent) 581 | Help_Window, QtBaseClass = uic.loadUiType("help.ui") 582 | self.ui = Help_Window() 583 | self.ui.setupUi(self) 584 | 585 | 586 | 587 | #Settings dialoge 588 | class Settings(QMainWindow): 589 | def __init__(self, parent=None): 590 | super(Settings, self).__init__(parent) 591 | Settings_Window, QtBaseClass = uic.loadUiType("settings.ui") 592 | self.ui = Settings_Window() 593 | self.ui.setupUi(self) 594 | 595 | self.ui.ChooseWorkingDir.clicked.connect(self.ChooseWorkingDir) 596 | self.ui.ChooseZemaxPath.clicked.connect(self.ChooseZemaxPath) 597 | self.ui.ChooseInventorPath.clicked.connect(self.ChooseInventorPath) 598 | 599 | self.ui.RayFieldCheck1.stateChanged.connect(self.Box_1) 600 | self.ui.RayFieldCheck2.stateChanged.connect(self.Box_2) 601 | self.ui.RayFieldCheck3.stateChanged.connect(self.Box_3) 602 | self.ui.RayFieldCheck4.stateChanged.connect(self.Box_4) 603 | 604 | self.ui.SaveRayFiles.clicked.connect(self.SaveRayFiles) 605 | 606 | self.showWorkingLabels() 607 | 608 | self.read_checkboxes() 609 | self.Box_1() 610 | self.Box_2() 611 | self.Box_3() 612 | self.Box_4() 613 | 614 | def read_checkboxes(self): 615 | self.config = configparser.ConfigParser() 616 | self.config.read('config.ini') 617 | 618 | checkbox_one = self.config.get('RAYFIELD', 'one_checked', fallback='') 619 | print('state checkbox_one' + checkbox_one) 620 | 621 | if checkbox_one == 'True': 622 | print('meepmeep') 623 | self.ui.RayFieldCheck1.setChecked(True) 624 | 625 | checkbox_two = self.config.get('RAYFIELD', 'two_checked', fallback='') 626 | if checkbox_two == 'True': 627 | self.ui.RayFieldCheck2.setChecked(True) 628 | 629 | checkbox_three = self.config.get('RAYFIELD', 'three_checked', fallback='') 630 | if checkbox_three == 'True': 631 | self.ui.RayFieldCheck3.setChecked(True) 632 | 633 | checkbox_four = self.config.get('RAYFIELD', 'four_checked', fallback='') 634 | if checkbox_four == 'True': 635 | self.ui.RayFieldCheck4.setChecked(True) 636 | 637 | def SaveRayFiles(self): 638 | self.config = configparser.ConfigParser() 639 | self.config.read("config.ini") 640 | self.config['RAYFIELD'] = {'one' : self.ui.RayField1.text(), 641 | 'one_checked' : self.ui.RayFieldCheck1.isChecked(), 642 | 'two' : self.ui.RayField2.text(), 643 | 'two_checked' : self.ui.RayFieldCheck2.isChecked(), 644 | 'three': self.ui.RayField3.text(), 645 | 'three_checked' : self.ui.RayFieldCheck3.isChecked(), 646 | 'four': self.ui.RayField4.text(), 647 | 'four_checked' : self.ui.RayFieldCheck4.isChecked(),} 648 | 649 | with open('config.ini', 'w') as configfile: 650 | self.config.write(configfile) 651 | self.showWorkingLabels() 652 | 653 | 654 | def Box_1(self): 655 | if self.ui.RayFieldCheck1.isChecked(): 656 | print('RayFieldCheck1 checked') 657 | self.ui.RayField1.setEnabled(True) 658 | 659 | self.config = configparser.ConfigParser() 660 | self.config.read('config.ini') 661 | print(self.config.sections()) 662 | self.Field1 = self.config.get('RAYFIELD', 'one', fallback='') 663 | self.ui.RayField1.setText(self.Field1) 664 | 665 | else: 666 | print('RayFieldCheck1 unchecked') 667 | self.ui.RayField1.setEnabled(False) 668 | 669 | def Box_2(self): 670 | if self.ui.RayFieldCheck2.isChecked(): 671 | print('RayFieldCheck2 checked') 672 | self.ui.RayField2.setEnabled(True) 673 | 674 | self.config = configparser.ConfigParser() 675 | self.config.read('config.ini') 676 | print(self.config.sections()) 677 | self.Field2 = self.config.get('RAYFIELD', 'two', fallback='') 678 | self.ui.RayField2.setText(self.Field2) 679 | 680 | else: 681 | print('RayFieldCheck2 unchecked') 682 | self.ui.RayField2.setEnabled(False) 683 | 684 | def Box_3(self): 685 | if self.ui.RayFieldCheck3.isChecked(): 686 | print('RayFieldCheck3 checked') 687 | self.ui.RayField3.setEnabled(True) 688 | 689 | self.config = configparser.ConfigParser() 690 | self.config.read('config.ini') 691 | print(self.config.sections()) 692 | self.Field3 = self.config.get('RAYFIELD', 'three', fallback='') 693 | self.ui.RayField3.setText(self.Field3) 694 | 695 | else: 696 | print('RayFieldCheck3 unchecked') 697 | self.ui.RayField3.setEnabled(False) 698 | 699 | def Box_4(self): 700 | if self.ui.RayFieldCheck4.isChecked(): 701 | print('RayFieldCheck4 checked') 702 | self.ui.RayField4.setEnabled(True) 703 | 704 | self.config = configparser.ConfigParser() 705 | self.config.read('config.ini') 706 | print(self.config.sections()) 707 | self.Field4 = self.config.get('RAYFIELD', 'four', fallback='') 708 | self.ui.RayField4.setText(self.Field4) 709 | 710 | else: 711 | print('RayFieldCheck3 unchecked') 712 | self.ui.RayField3.setEnabled(False) 713 | 714 | def showWorkingLabels(self): 715 | self.ui.label.setText('Working Dir not set or invalid') 716 | self.config = configparser.ConfigParser() 717 | self.config.read("config.ini") 718 | print(self.config.sections()) 719 | self.configDir = self.config.get('DEFAULT', 'WorkingDir', fallback='Working Dir not set') 720 | self.ui.label.setText(self.configDir) 721 | 722 | self.ZemaxPath = self.config.get('ZEMAX', 'zemaxexec', fallback='ZEMAX Path not set') 723 | self.ui.ZemaxPath.setText(self.ZemaxPath) 724 | 725 | self.InventorPath = self.config.get('INVENTOR', 'inventorexec', fallback='Inventor Path not set') 726 | self.ui.InventorPath.setText(self.InventorPath) 727 | 728 | def ChooseZemaxPath(self): 729 | path = QFileDialog.getOpenFileName(self,"Zemax Path", "","All Files (*);;Executable (*.exe)")[0] 730 | self.config = configparser.ConfigParser() 731 | self.config.read("config.ini") 732 | self.config['ZEMAX'] = {'zemaxexec' : path} 733 | with open('config.ini', 'w') as configfile: 734 | self.config.write(configfile) 735 | self.showWorkingLabels() 736 | 737 | def ChooseInventorPath(self): 738 | path = QFileDialog.getOpenFileName(self,"Inventor Path", "","All Files (*);;Executable (*.exe)")[0] 739 | self.config = configparser.ConfigParser() 740 | self.config.read("config.ini") 741 | self.config['INVENTOR'] = {'inventorexec' : path} 742 | with open('config.ini', 'w') as configfile: 743 | self.config.write(configfile) 744 | self.showWorkingLabels() 745 | print(path) 746 | 747 | def ChooseWorkingDir(self): 748 | file = QFileDialog.getExistingDirectory(None, 749 | 'Open working directory', 750 | None, 751 | QFileDialog.ShowDirsOnly) 752 | self.config = configparser.ConfigParser() 753 | self.config.read("config.ini") 754 | self.config['DEFAULT'] = {'WorkingDir' : file} 755 | with open('config.ini', 'w') as configfile: 756 | self.config.write(configfile) 757 | self.showWorkingLabels() 758 | 759 | if __name__ == '__main__': 760 | app = QApplication(sys.argv) 761 | window = MyApp() 762 | window.show() 763 | sys.exit(app.exec_()) 764 | --------------------------------------------------------------------------------