pytecplot 1.5.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. pytecplot-1.5.2.dist-info/LICENSE.html +77 -0
  2. pytecplot-1.5.2.dist-info/METADATA +508 -0
  3. pytecplot-1.5.2.dist-info/RECORD +97 -0
  4. pytecplot-1.5.2.dist-info/WHEEL +5 -0
  5. pytecplot-1.5.2.dist-info/top_level.txt +1 -0
  6. tecplot/__init__.py +94 -0
  7. tecplot/annotation/__init__.py +7 -0
  8. tecplot/annotation/annotation.py +223 -0
  9. tecplot/annotation/geometry.py +369 -0
  10. tecplot/annotation/georeference.py +33 -0
  11. tecplot/annotation/image.py +185 -0
  12. tecplot/annotation/iter.py +105 -0
  13. tecplot/annotation/polyline.py +381 -0
  14. tecplot/annotation/text.py +744 -0
  15. tecplot/constant.py +401 -0
  16. tecplot/data/__init__.py +34 -0
  17. tecplot/data/array.py +645 -0
  18. tecplot/data/create.py +1 -0
  19. tecplot/data/dataset.py +1809 -0
  20. tecplot/data/extract.py +544 -0
  21. tecplot/data/face_neighbors.py +494 -0
  22. tecplot/data/facemap.py +814 -0
  23. tecplot/data/load.py +2075 -0
  24. tecplot/data/nodemap.py +577 -0
  25. tecplot/data/operate.py +928 -0
  26. tecplot/data/query.py +334 -0
  27. tecplot/data/save.py +343 -0
  28. tecplot/data/variable.py +280 -0
  29. tecplot/data/zone.py +841 -0
  30. tecplot/exception.py +201 -0
  31. tecplot/export/__init__.py +13 -0
  32. tecplot/export/animation.py +521 -0
  33. tecplot/export/export_setup.py +318 -0
  34. tecplot/export/image.py +577 -0
  35. tecplot/export/print_setup.py +218 -0
  36. tecplot/export/time_animation.py +607 -0
  37. tecplot/extension/__init__.py +14 -0
  38. tecplot/extension/ipython.py +68 -0
  39. tecplot/layout/__init__.py +20 -0
  40. tecplot/layout/frame.py +1285 -0
  41. tecplot/layout/layout.py +488 -0
  42. tecplot/layout/page.py +541 -0
  43. tecplot/legend/__init__.py +4 -0
  44. tecplot/legend/contour_legend.py +516 -0
  45. tecplot/legend/legend.py +141 -0
  46. tecplot/legend/line_legend.py +66 -0
  47. tecplot/legend/rgb_coloring_legend.py +291 -0
  48. tecplot/legend/scatter_legend.py +136 -0
  49. tecplot/macro.py +339 -0
  50. tecplot/plot/__init__.py +62 -0
  51. tecplot/plot/axes.py +881 -0
  52. tecplot/plot/axis.py +1350 -0
  53. tecplot/plot/blanking.py +584 -0
  54. tecplot/plot/contour.py +1302 -0
  55. tecplot/plot/effects.py +162 -0
  56. tecplot/plot/fieldmap.py +2159 -0
  57. tecplot/plot/grid.py +758 -0
  58. tecplot/plot/isosurface.py +1167 -0
  59. tecplot/plot/labels.py +338 -0
  60. tecplot/plot/linemap.py +1905 -0
  61. tecplot/plot/linking.py +489 -0
  62. tecplot/plot/plot.py +1791 -0
  63. tecplot/plot/rgb_coloring.py +281 -0
  64. tecplot/plot/scatter.py +396 -0
  65. tecplot/plot/slice.py +1544 -0
  66. tecplot/plot/streamtrace.py +1949 -0
  67. tecplot/plot/symbol.py +567 -0
  68. tecplot/plot/ticks.py +666 -0
  69. tecplot/plot/title.py +380 -0
  70. tecplot/plot/vector.py +679 -0
  71. tecplot/plot/view.py +868 -0
  72. tecplot/session/__init__.py +112 -0
  73. tecplot/session/aux_data.py +276 -0
  74. tecplot/session/config.py +92 -0
  75. tecplot/session/session.py +425 -0
  76. tecplot/session/state_changed.py +225 -0
  77. tecplot/session/style.py +504 -0
  78. tecplot/tecutil/__init__.py +16 -0
  79. tecplot/tecutil/arglist.py +230 -0
  80. tecplot/tecutil/captured_output.py +177 -0
  81. tecplot/tecutil/constant.py +1312 -0
  82. tecplot/tecutil/index_set.py +118 -0
  83. tecplot/tecutil/lock.py +36 -0
  84. tecplot/tecutil/message_pb2.py +39 -0
  85. tecplot/tecutil/preamble.py +54 -0
  86. tecplot/tecutil/stringlist.py +114 -0
  87. tecplot/tecutil/sv.py +1662 -0
  88. tecplot/tecutil/tecutil.py +12402 -0
  89. tecplot/tecutil/tecutil_client.py +627 -0
  90. tecplot/tecutil/tecutil_connector.py +937 -0
  91. tecplot/tecutil/tecutil_rpc.py +6845 -0
  92. tecplot/tecutil/util.py +332 -0
  93. tecplot/text/__init__.py +3 -0
  94. tecplot/text/font.py +224 -0
  95. tecplot/text/label_format.py +374 -0
  96. tecplot/text/textbox.py +106 -0
  97. tecplot/version.py +41 -0
@@ -0,0 +1,937 @@
1
+ import os
2
+ import calendar
3
+ import contextlib
4
+ import ctypes
5
+ import logging
6
+ import platform
7
+ import re
8
+ import struct
9
+ import sys
10
+ import time
11
+
12
+ from ctypes import (c_char, c_char_p, c_int, c_int32, c_size_t, c_void_p,
13
+ POINTER, pointer, c_bool, cast, byref, util)
14
+ from collections import Counter, namedtuple
15
+ from datetime import date
16
+ from enum import Enum
17
+ from locale import setlocale, LC_NUMERIC
18
+ from os import path
19
+ from subprocess import Popen, PIPE
20
+ from textwrap import dedent
21
+ from warnings import warn
22
+
23
+ from .captured_output import captured_output
24
+ from ..constant import *
25
+ from ..exception import *
26
+ from .tecutil import TecUtil
27
+
28
+
29
+ log = logging.getLogger(__name__)
30
+
31
+
32
+ class ManagerStartReturnCode(Enum):
33
+ Ok = 0
34
+ HomeDirectoryNotSpecified = 1
35
+ LicenseFileNotFound = 2
36
+ LicenseIsInvalid = 3
37
+ LicenseExpired = 4
38
+ InternalInitializationError = 5
39
+ EngineUninitialized = 6
40
+ LicenseFileContainsPermanent = 7
41
+
42
+ def to_string(self, tecutil_connector):
43
+ return tecutil_connector.handle \
44
+ .ManagerStartReturnCodeString(self.value).value.decode()
45
+
46
+
47
+ def find_file(filenames, searchpaths):
48
+ if isinstance(searchpaths, str):
49
+ searchpaths = searchpaths.split(os.pathsep)
50
+ for filename in filenames:
51
+ for searchpath in searchpaths:
52
+ fpath = path.join(searchpath, filename)
53
+ if path.exists(fpath):
54
+ return fpath
55
+
56
+
57
+ SDKVersion = namedtuple('SDKVersion', ['MajorVersion', 'MinorVersion',
58
+ 'MajorRevision', 'MinorRevision'])
59
+
60
+
61
+ class allocated_c_char_p(c_char_p):
62
+ def __del__(self):
63
+ libc = ctypes.CDLL('msvcrt' if platform.system() == 'Windows' else None)
64
+ libc.free(self)
65
+
66
+
67
+ class TecUtilConnector(object):
68
+
69
+ Message = namedtuple('Message', ['level', 'message'])
70
+
71
+ def __init__(self):
72
+ if __debug__:
73
+ # ensure log messages get out to console
74
+ logging.basicConfig()
75
+
76
+ self.handle = None
77
+ self.tecutil_handle = None
78
+ self.libbatch_path = None
79
+ self.started = None
80
+ self.stopped = None
81
+ self._last_message = None
82
+ self._load_library_error = None
83
+
84
+ self.client = None
85
+
86
+ self.suspended = False
87
+ self.state_changes = {}
88
+
89
+ if __debug__:
90
+ self._tecutil_call_count = Counter()
91
+ self._style_call_count = {'GET': Counter(), 'SET': Counter()}
92
+
93
+ if ctypes.sizeof(c_void_p) != 8:
94
+ msg = '64-bit Python is required to use PyTecplot'
95
+ raise TecplotLibraryNotLoadedError(msg)
96
+
97
+ def bind_local_objects(self):
98
+ log.debug('binding local objects')
99
+ import tecplot
100
+ tecplot.tecutil.ArgList = tecplot.tecutil.LocalArgList
101
+ tecplot.tecutil.IndexSet._bind_tecutil()
102
+ tecplot.tecutil.StringList._bind_tecutil()
103
+
104
+ def bind_remote_objects(self):
105
+ import tecplot
106
+ tecplot.tecutil.ArgList = tecplot.tecutil.RemoteArgList
107
+ tecplot.tecutil.IndexSet._bind_tecutil()
108
+ tecplot.tecutil.StringList._bind_tecutil()
109
+
110
+ @property
111
+ def connected(self):
112
+ return self.client is not None and self.client.connected
113
+
114
+ def connect(self, host='localhost', port=7600, timeout=10, quiet=False):
115
+ try:
116
+ from tecplot.tecutil.tecutil_client import TecUtilClient
117
+ self.client = TecUtilClient()
118
+ self.client.connect(host, port, timeout, quiet=quiet)
119
+ except (ImportError, TecplotSystemError, TecplotInvalidMessage) as err_pb:
120
+ self.client = None
121
+ if isinstance(err_pb, (ImportError, TecplotSystemError)):
122
+ # no protobuf installed
123
+ msg = 'required Python module: protobuf'
124
+ raise ImportError(msg)
125
+ else:
126
+ # protobuf installed, both not working
127
+ msg = 'invalid message received on port {}'.format(port)
128
+ raise TecplotInvalidMessage(msg + '\n' + str(err_pb))
129
+
130
+ self.update_sdk_version()
131
+
132
+ if self.sdk_version_info < (2020, 1):
133
+ msg = 'PyTecplot requires Tecplot 360 version 2020 R1 or later'
134
+ raise TecplotOutOfDateEngineError((2020, 1), msg)
135
+
136
+ self.bind_local_objects()
137
+ self.client.patch_tecutil()
138
+
139
+ def disconnect(self):
140
+ self.bind_remote_objects()
141
+ self.client.disconnect()
142
+ self.update_sdk_version()
143
+ log.info('Disconnected from Tecplot 360 instance.')
144
+
145
+ @property
146
+ def tecsdkhome(self):
147
+ if not hasattr(self, '_tecsdkhome'):
148
+ if self.connected:
149
+ self._tecsdkhome = _tecutil.TecplotGetHomeDirectory()
150
+ else:
151
+ try:
152
+ self._tecsdkhome = os.environ['TECSDKHOME']
153
+ if not path.isdir(self._tecsdkhome):
154
+ raise OSError
155
+ except (KeyError, OSError):
156
+ if self.libbatch_path is not None:
157
+ log.debug('reading tecutilbatch path to get SDK home')
158
+ tecutilbatch_dir = path.dirname(self.libbatch_path)
159
+ self._tecsdkhome = path.dirname(tecutilbatch_dir)
160
+ else:
161
+ self._tecsdkhome = ''
162
+ return self._tecsdkhome
163
+
164
+ def init_local_library(self):
165
+ setlocale(LC_NUMERIC, 'C')
166
+ os.environ['LC_NUMERIC'] = 'C'
167
+
168
+ load = {'Darwin': self.load_darwin,
169
+ 'Linux': self.load_linux,
170
+ 'Windows': self.load_windows}
171
+
172
+ log.debug('Attempting to load Tecplot batch library')
173
+ load[platform.system()]()
174
+ msg = 'Successfully loaded Tecplot batch library: {}'
175
+ log.info(msg.format(self.libbatch_path))
176
+
177
+ self.handle.AcquireLicense.restype = c_bool
178
+ self.handle.GetTUAssertErrorMessage.restype = c_char_p
179
+ self.handle.LicenseExpirationDate.restype = c_char_p
180
+ self.handle.LicenseInfo.restype = c_char_p
181
+ self.handle.LicenseIsRoaming.restype = c_bool
182
+ self.handle.LicenseIsValid.restype = c_bool
183
+ self.handle.LicenseStartRoaming.argtypes = (c_int,)
184
+ self.handle.LicenseStartRoaming.restype = c_bool
185
+ self.handle.LicenseStopRoaming.argtypes = (c_bool,)
186
+ self.handle.LicenseStopRoaming.restype = c_bool
187
+ self.handle.ReleaseLicense.restype = c_bool
188
+ try:
189
+ self.handle.TranslateMacroToPython.restype = POINTER(c_char)
190
+ self.handle.TranslateMacroWithRawDataToPython.restype = POINTER(c_char)
191
+ except:
192
+ log.debug('engine does not support macro translations')
193
+
194
+ _tecutil.configure_tecutil_argtypes()
195
+ self.bind_remote_objects()
196
+ self.update_sdk_version()
197
+
198
+ if self.sdk_version_info < (2020, 1):
199
+ msg = 'PyTecplot requires Tecplot 360 version 2020 R1 or later'
200
+ raise TecplotOutOfDateEngineError((2020, 1), msg)
201
+
202
+ self.handle.Start.argtypes = (c_int, POINTER(c_char_p),)
203
+ self.handle.ManagerStartReturnCodeString.argtypes = (c_int,)
204
+ self.handle.ManagerStartReturnCodeString.restype = allocated_c_char_p
205
+ self.handle.Start.restype = ManagerStartReturnCode
206
+
207
+ self.patch_tecutil()
208
+
209
+ def patch_tecutil(self):
210
+ '''Modify TecUtil interface according to historical changes in SDK.
211
+
212
+ 2021.2
213
+ Facemap and FaceNeighbor connections now use zone unique IDs
214
+ (64-bit integers) instead of indices (32-bit integers).
215
+ Methods affected:
216
+ * Facemap.set_boundary_connections()
217
+ * FaceNeighbors.add_neighbors()
218
+
219
+ see also: TecUtilConnector.patch_tecutil()
220
+ '''
221
+ if self.sdk_version_info < (2021, 2):
222
+ for fn in (
223
+ _tecutil.handle.tecUtilDataFaceMapAssignBConns,
224
+ _tecutil.handle.tecUtilDataFaceMapAssignBConns64,
225
+ _tecutil.handle.tecUtilDataFaceNbrAssignByRef,
226
+ _tecutil.handle.tecUtilDataFaceNbrAssignByRef64,
227
+ ):
228
+ fn.argtypes = fn.argtypes[:-1] + [POINTER(c_int32)]
229
+
230
+ def load_linux(self):
231
+
232
+ def _syslibpath():
233
+ ret = []
234
+ try:
235
+ log.debug('acquiring system library search path using ldconfig')
236
+ cmd = 'ldconfig -v -N'
237
+ proc = Popen(cmd, shell=True,
238
+ executable=os.environ.get('SHELL', '/bin/bash'),
239
+ env=os.environ, stdout=PIPE, stderr=PIPE)
240
+ ptrn = re.compile(r'^\t')
241
+ for line in proc.communicate()[0][:-1].decode().split('\n'):
242
+ if not ptrn.match(line):
243
+ d = line.split(':')[0]
244
+ if path.exists(d) and path.isdir(d):
245
+ ret.append(d)
246
+ except:
247
+ log.debug('diagnostic command failed: ' + cmd)
248
+ return ret
249
+
250
+ def _missinglibs(lib):
251
+ ret = []
252
+ try:
253
+ log.debug('looking for missing libs using ldd (clean env)')
254
+ cmd = 'ldd ' + lib
255
+ proc = Popen(cmd, shell=True,
256
+ executable=os.environ.get('SHELL', '/bin/bash'),
257
+ #env=os.environ,
258
+ stdout=PIPE, stderr=PIPE)
259
+ out, err = proc.communicate()
260
+ if 'command not found' not in err.decode():
261
+ if len(out.decode().split('\n')) < 3:
262
+ log.debug(out.decode())
263
+ for line in out.decode().split('\n'):
264
+ if line.endswith('not found'):
265
+ ret.append(line.split()[0])
266
+ except:
267
+ log.debug('diagnostic command failed: ' + cmd)
268
+ return ret
269
+
270
+ def _find_library(libnames):
271
+ libpath = os.environ.get('LD_LIBRARY_PATH', None)
272
+ for libname in libnames:
273
+ ret = None
274
+ if libpath is not None:
275
+ ret = find_file([libname], libpath)
276
+ if ret is None:
277
+ ret = find_file([libname], _syslibpath())
278
+ if ret is not None:
279
+ return ret
280
+
281
+ def _dl_abspath(libname):
282
+ """Returns the absolute path of the loaded dynamic library"""
283
+ log.debug('using libc to get path to tecutilbatch library')
284
+
285
+ class dl_phdr_info(ctypes.Structure):
286
+ _fields_ = [
287
+ ('padding0', c_void_p), # ignore it
288
+ ('dlpi_name', c_char_p)]
289
+ callback_t = ctypes.CFUNCTYPE(c_int, POINTER(dl_phdr_info),
290
+ POINTER(c_size_t), c_char_p)
291
+ _dl_abspath.lib_abspath = None
292
+
293
+ def callback(info, size, data):
294
+ if data in info.contents.dlpi_name:
295
+ _dl_abspath.lib_abspath = \
296
+ info.contents.dlpi_name.decode()
297
+ return 0
298
+
299
+ libc = ctypes.CDLL(None)
300
+ libc.dl_iterate_phdr.argtypes = [callback_t, c_char_p]
301
+ libc.dl_iterate_phdr.restype = c_int
302
+ libc.dl_iterate_phdr(callback_t(callback), libname.encode())
303
+ return _dl_abspath.lib_abspath
304
+
305
+ libfmt = 'lib{name}.so'
306
+ libbatch = libfmt.format(name='tecutilbatch')
307
+ libchecked = libfmt.format(name='tecutilchecked')
308
+ libnames = [libbatch, libchecked]
309
+
310
+ cdllexcept = {}
311
+ try:
312
+ self.handle = ctypes.cdll.LoadLibrary(libbatch)
313
+ self.tecutil_handle = ctypes.cdll.LoadLibrary(libchecked)
314
+ if self.handle is None or self.tecutil_handle is None:
315
+ raise OSError
316
+ else:
317
+ self.libbatch_path = _dl_abspath(libbatch)
318
+ return
319
+ except (OSError, TypeError) as e:
320
+ exc_type, exc_obj, exc_tb = sys.exc_info()
321
+ fname = path.split(exc_tb.tb_frame.f_code.co_filename)[1]
322
+ info = [str(x) for x in [e, exc_type,
323
+ '{}:{}'.format(fname, exc_tb.tb_lineno)]]
324
+ cdllexcept[libbatch] = '\n'.join(info)
325
+
326
+ if self.handle is None:
327
+ # fall-back to tecinterprocess
328
+ try:
329
+ libinterproc = libfmt.format(name='tecinterprocess')
330
+ self.handle = ctypes.cdll.LoadLibrary(libinterproc)
331
+ self.tecutil_handle = self.handle
332
+ if self.handle is None:
333
+ raise OSError
334
+ else:
335
+ self.libbatch_path = _dl_abspath(libinterproc)
336
+ return
337
+ except (OSError, TypeError) as e:
338
+ exc_type, exc_obj, exc_tb = sys.exc_info()
339
+ fname = path.split(exc_tb.tb_frame.f_code.co_filename)[1]
340
+ info = [str(x) for x in [e, exc_type,
341
+ '{}:{}'.format(fname, exc_tb.tb_lineno)]]
342
+ cdllexcept[libinterproc] = '\n'.join(info)
343
+
344
+ msg = dedent('''\
345
+ The Tecplot batch library or one of its dependencies could not be
346
+ found. This is usually the result of not setting the environment
347
+ path LD_LIBRARY_PATH to the directory containing the tecplot
348
+ executables.
349
+
350
+ LD_LIBRARY_PATH={libpath}
351
+
352
+ Library file name(s):
353
+ {libnames}
354
+ ''').format(libpath=os.environ.get('LD_LIBRARY_PATH', None),
355
+ libnames='\n '.join(libnames))
356
+
357
+ self.libbatch_path = _find_library([libbatch])
358
+ if self.libbatch_path is None:
359
+ raise TecplotLibraryNotFoundError(msg)
360
+
361
+ msg += dedent('''
362
+ Found batch library file:
363
+ {foundlib}
364
+
365
+ Your Tecplot 360 EX may be out of date. Please install the latest
366
+ version of 360 EX which can be obtained here:
367
+ https://www.tecplot.com/downloads
368
+ ''').format(foundlib=self.libbatch_path)
369
+
370
+ missinglibs = _missinglibs(self.libbatch_path)
371
+ if len(missinglibs):
372
+ msg += dedent('''
373
+ Missing libraries:
374
+ {missinglibs}
375
+ ''').format(missinglibs='\n '.join(missinglibs))
376
+
377
+ lib = path.basename(self.libbatch_path)
378
+ msg += dedent('''
379
+ Python ctypes exception caught while trying to load library:
380
+ {cdllexcept}
381
+ ''').format(cdllexcept='\n '.join(cdllexcept[lib].split('\n')))
382
+
383
+ raise TecplotLibraryNotLoadedError(msg)
384
+
385
+ def load_darwin(self):
386
+
387
+ def _missinglibs(lib):
388
+ ret = []
389
+ try:
390
+ log.debug('looking for missing libs using otool')
391
+ cmd = 'otool -L ' + lib
392
+ proc = Popen(cmd, shell=True,
393
+ executable=os.environ.get('SHELL', '/bin/bash'),
394
+ env=os.environ, stdout=PIPE, stderr=PIPE)
395
+ out, err = proc.communicate()
396
+ if 'command not found' not in err.decode():
397
+ for line in out.decode().split('\n'):
398
+ if line.endswith('not found'):
399
+ ret.append(line.split()[0])
400
+ except:
401
+ log.debug('diagnostic command failed: ' + cmd)
402
+ return ret
403
+
404
+ def _find_library(libnames):
405
+ for libname in libnames:
406
+ ret = util.find_library(libname)
407
+ if ret is not None:
408
+ return ret
409
+
410
+ libfmt = 'lib{name}.so'
411
+ libbatch = libfmt.format(name='tecutilbatch')
412
+ libchecked = libfmt.format(name='tecutilchecked')
413
+ libnames = [libbatch, libchecked]
414
+
415
+ cdllexcept = {}
416
+ self.libbatch_path = _find_library([libbatch])
417
+ try:
418
+ self.handle = ctypes.cdll.LoadLibrary(libbatch)
419
+ self.tecutil_handle = ctypes.cdll.LoadLibrary(libchecked)
420
+ if self.handle is None or self.tecutil_handle is None:
421
+ raise OSError
422
+ else:
423
+ return
424
+ except (OSError, TypeError) as e:
425
+ exc_type, exc_obj, exc_tb = sys.exc_info()
426
+ fname = path.split(exc_tb.tb_frame.f_code.co_filename)[1]
427
+ info = [str(x) for x in [e, exc_type, fname, exc_tb.tb_lineno]]
428
+ cdllexcept[libbatch] = '\n'.join(info)
429
+
430
+ if self.handle is None:
431
+ # fall-back to tecinterprocess
432
+ try:
433
+ libinterproc = libfmt.format(name='tecinterprocess')
434
+ self.libbatch_path = _find_library([libinterproc])
435
+ self.handle = ctypes.cdll.LoadLibrary(libinterproc)
436
+ self.tecutil_handle = self.handle
437
+ if self.handle is None:
438
+ raise OSError
439
+ else:
440
+ return
441
+ except (OSError, TypeError) as e:
442
+ exc_type, exc_obj, exc_tb = sys.exc_info()
443
+ fname = path.split(exc_tb.tb_frame.f_code.co_filename)[1]
444
+ info = [str(x) for x in [e, exc_type,
445
+ '{}:{}'.format(fname, exc_tb.tb_lineno)]]
446
+ cdllexcept[libinterproc] = '\n'.join(info)
447
+
448
+ msg = dedent('''\
449
+ The Tecplot batch library or one of its dependencies could not be
450
+ found. This is usually the result of not setting the environment
451
+ path DYLD_LIBRARY_PATH to the directory containing the tecplot
452
+ executables.
453
+
454
+ DYLD_LIBRARY_PATH={libpath}
455
+
456
+ Batch library file name(s):
457
+ {libnames}
458
+ ''').format(libpath=os.environ.get('DYLD_LIBRARY_PATH', None),
459
+ libnames='\n '.join(libnames))
460
+
461
+ if self.libbatch_path is None:
462
+ raise TecplotLibraryNotFoundError(msg)
463
+
464
+ msg += dedent('''
465
+ Found batch library file:
466
+ {foundlib}
467
+
468
+ Your Tecplot 360 EX may be out of date. Please install the latest
469
+ version of 360 EX which can be obtained here:
470
+ https://www.tecplot.com/downloads
471
+ ''').format(foundlib=self.libbatch_path)
472
+
473
+ missinglibs = _missinglibs(self.libbatch_path)
474
+ if len(missinglibs):
475
+ msg += dedent('''
476
+ Missing libraries:
477
+ {missinglibs}
478
+ ''').format(missinglibs='\n '.join(missinglibs))
479
+
480
+ lib = path.basename(self.libbatch_path)
481
+ msg += dedent('''
482
+ Python ctypes exception caught while trying to load library:
483
+ {cdllexcept}
484
+ ''').format(cdllexcept='\n '.join(cdllexcept[lib].split('\n')))
485
+
486
+ raise TecplotLibraryNotLoadedError(msg)
487
+
488
+ def load_windows(self):
489
+
490
+ def _load_library(libbatch, libchecked=None):
491
+ try:
492
+ self.handle = ctypes.cdll.LoadLibrary(libbatch)
493
+ if self.handle is None:
494
+ return False, 'Failed to load: {}'.format(libbatch)
495
+ if libchecked:
496
+ self.tecutil_handle = ctypes.cdll.LoadLibrary(libchecked)
497
+ if self.tecutil_handle is None:
498
+ return False, 'Failed to load: {}'.format(libchecked)
499
+ else:
500
+ self.tecutil_handle = self.handle
501
+ if not os.path.isabs(libbatch):
502
+ libbatch = util.find_library(os.path.splitext(libbatch)[0])
503
+ return True, libbatch
504
+ except OSError as e:
505
+ exc_type, exc_obj, exc_tb = sys.exc_info()
506
+ fname = path.split(exc_tb.tb_frame.f_code.co_filename)[1]
507
+ info = [str(x) for x in [e, exc_type, fname,
508
+ exc_tb.tb_lineno]]
509
+ return False, '\n'.join(info)
510
+
511
+ @contextlib.contextmanager
512
+ def _patch_path(libdir):
513
+ if libdir is None:
514
+ if sys.version_info >= (3, 8):
515
+ for p in filter(None, os.environ['PATH'].split(os.pathsep)):
516
+ os.add_dll_directory(p)
517
+ yield
518
+ else:
519
+ original_path = os.environ['PATH']
520
+ try:
521
+ if libdir and sys.version_info >= (3, 8):
522
+ os.add_dll_directory(libdir)
523
+ os.environ['PATH'] = libdir + os.pathsep + os.environ['PATH']
524
+ yield
525
+ finally:
526
+ os.environ['PATH'] = original_path
527
+
528
+ cdllexcept = []
529
+ for libs in (('tecutilbatch.dll', 'tecutilchecked.dll'),
530
+ ('tecinterprocess.dll',),
531
+ ('tecutilbatchd.dll', 'tecutilcheckedd.dll')):
532
+
533
+ # convert libs to absolute paths if possible
534
+ libdir = None
535
+ for d in os.environ['PATH'].split(os.pathsep):
536
+ abslibs = [os.path.join(d, lib) for lib in libs]
537
+ if all(os.path.exists(f) for f in abslibs):
538
+ libs = abslibs
539
+ libdir = d
540
+ break
541
+
542
+ with _patch_path(libdir):
543
+ success, msg = _load_library(*libs)
544
+ if success:
545
+ self.libbatch_path = msg
546
+ return
547
+ else:
548
+ cdllexcept.append(msg)
549
+
550
+ msg = dedent('''\
551
+ The Tecplot batch library or one of its dependencies could not be
552
+ found. This is usually the result of not setting the environment
553
+ path PATH to the directory containing the tecplot executables.
554
+ Alternatively, your Tecplot 360 EX may be out of date. Please
555
+ install the latest version of 360 EX which can be obtained here:
556
+ https://www.tecplot.com/downloads
557
+
558
+ Batch library file names:
559
+ tecutilbatch.dll
560
+ tecutilchecked.dll
561
+
562
+ PATH={libpath}
563
+
564
+ Found batch library file:
565
+ {foundlib}
566
+ ''').format(libpath=os.environ.get('PATH', None),
567
+ foundlib=self.libbatch_path)
568
+
569
+ if cdllexcept:
570
+ info = '\n '.join(cdllexcept[0].split('\n'))
571
+ # Error 127 == Missing dll symbol.
572
+ if 'Error 127' in info:
573
+ info += ('\n***NOTE: If your script uses PyQt, ' +
574
+ 'you must import tecplot before importing PyQt.\n')
575
+ msg += dedent('''
576
+ Python ctypes exception caught while trying to load library:
577
+ {cdllexcept}
578
+ ''').format(cdllexcept=info)
579
+
580
+ if self.libbatch_path is None:
581
+ raise TecplotLibraryNotFoundError(msg)
582
+
583
+ raise TecplotLibraryNotLoadedError(msg)
584
+
585
+ def start(self):
586
+ if self.started:
587
+ return
588
+
589
+ self._license_check_count = 0
590
+
591
+ if self.handle is None:
592
+ try:
593
+ self.init_local_library()
594
+ except KeyError as e:
595
+ self._load_library_error = TecplotLibraryNotFoundError, str(e)
596
+ except AttributeError as e:
597
+ log.error(e)
598
+ self._load_library_error = TecplotOutOfDateEngineError, (2020, 1, 0)
599
+ except Exception as e:
600
+ log.debug(e)
601
+ self._load_library_error = type(e), str(e)
602
+
603
+ if self.handle is None or self.libbatch_path is None:
604
+ if self._load_library_error is not None:
605
+ Err, msg = self._load_library_error
606
+ raise Err(msg)
607
+ else:
608
+ raise TecplotLibraryNotLoadedError
609
+
610
+ if self.stopped:
611
+ msg = 'PyTecplot cannot be restarted after it has been stopped.'
612
+ raise TecplotLibraryNotLoadedError(msg)
613
+
614
+ log.debug('Attempting to start Tecplot engine')
615
+ msg = 'tecutilbatch path: {0} [{1}]'
616
+ log.debug(msg.format(self.libbatch_path,
617
+ time.ctime(os.stat(self.libbatch_path).st_mtime)))
618
+
619
+ log.debug('SDK home: "{}"'.format(self.tecsdkhome))
620
+
621
+ # Suppress the batch log file from the SDK, can be reenabled
622
+ # by setting the BATCHLOGFILE environment variable.
623
+ if 'BATCHLOGFILE' not in os.environ:
624
+ os.putenv('BATCHLOGFILE', os.devnull)
625
+
626
+ with captured_output(log.debug):
627
+ result = None
628
+ try:
629
+ # The Linux version of the SDK, 2020 and newer, passes SDK
630
+ # configuration command line arguments required to ensure
631
+ # that the SDK is started with a configuration that matches
632
+ # the libraries loaded as prepared by the tec360-env script.
633
+ # Examples are: --disable-FBOs, --osmesa, and --mesa.
634
+ args = [sys.executable, '-h', self.tecsdkhome]
635
+ args.extend(os.environ.get('TECPLOT_SDK_CONFIG','').split())
636
+ log.debug('args: '+' '.join(args))
637
+
638
+ argc = len(args)
639
+ argv = (c_char_p * len(args))(*[a.encode() for a in args])
640
+ result = self.handle.Start(argc, argv)
641
+ except Exception:
642
+ msg = 'Error initializing Tecplot engine. Your Tecplot 360 ' + \
643
+ 'installation may be out of date.'
644
+ raise TecplotInitializationError(msg)
645
+
646
+ if self.update_last_message():
647
+ self.log_last_message()
648
+ if self.last_message.level >= log.getEffectiveLevel():
649
+ raise TecplotInitializationError(self.last_message.message)
650
+
651
+ # Many error codes are license related, but not all of the them.
652
+ # We don't want to imply that there was a license error if the problem
653
+ # is not license related. For example, if batch.log is not writable,
654
+ # the return code will be "InternalInitializationError".
655
+ # In that case it would be misleading to throw a license error.
656
+
657
+ # Therefore, we are careful to throw a license error only
658
+ # if the error code is license related, and
659
+ # otherwise throw a TecplotSystemError.
660
+ if result != ManagerStartReturnCode.Ok:
661
+ errmsg = result.to_string(self)
662
+ if result in [ManagerStartReturnCode.LicenseExpired,
663
+ ManagerStartReturnCode.LicenseIsInvalid,
664
+ ManagerStartReturnCode.LicenseFileNotFound,
665
+ ManagerStartReturnCode.LicenseFileContainsPermanent]:
666
+ raise TecplotLicenseError(errmsg)
667
+ elif result != ManagerStartReturnCode.Ok:
668
+ raise TecplotSystemError(errmsg)
669
+
670
+ log.info('Tecplot engine started - license acquired')
671
+ info = self.handle.LicenseInfo().decode()
672
+ log.info('License information:\n ' + info.replace('\n', '\n '))
673
+ self.started = True
674
+
675
+ expire = self.license_expiration
676
+ if isinstance(expire, date):
677
+ days_left = (expire - date.today()).days
678
+ if days_left < 31:
679
+ if self.license_is_roaming:
680
+ warning_msg = '''
681
+ Your roaming license will expire in **{} days**.
682
+ '''
683
+ else:
684
+ warning_msg = '''
685
+ Your Tecplot software maintenance subscription
686
+ (TecPLUS) will expire in **{} days**, after which you
687
+ will no longer be able to use PyTecplot. Contact
688
+ sales@tecplot.com to renew your TecPLUS subscription.
689
+ '''
690
+ warning_msg = dedent(warning_msg).format(days_left)
691
+ warn(warning_msg)
692
+
693
+ if self.update_last_message():
694
+ # at this point, the engine has started successfully,
695
+ # but there was an error message.
696
+ # Perhaps an addon did not load correctly.
697
+ lvl = self.last_message.level
698
+ msg = self.last_message.message
699
+ if msg:
700
+ if lvl > logging.WARNING: # downgrade message to warning
701
+ lvl = logging.WARNING
702
+ log.log(lvl, msg)
703
+
704
+ def stop(self):
705
+ if self.stopped:
706
+ return
707
+ elif self.started and not self.connected:
708
+ # On macOS and prior to SDK v2022.1, calling stop from pytecplot would
709
+ # cause a crash on exit for certain edge cases.
710
+ if platform.system() == 'Darwin' and self.sdk_version_info < (2022, 1):
711
+ self.release_license()
712
+ else:
713
+ with captured_output(log.debug):
714
+ self.handle.Stop()
715
+ log.info('Tecplot engine stopped - license released')
716
+ self.started = False
717
+ self.stopped = True
718
+
719
+ if __debug__:
720
+ if log.isEnabledFor(logging.DEBUG):
721
+ fmt = '{: >8d} {}'
722
+ call_data = (('TecUtil', self._tecutil_call_count),
723
+ ('Get style', self._style_call_count['GET']),
724
+ ('Set style', self._style_call_count['SET']))
725
+ for call_type, calls in call_data:
726
+ if calls:
727
+ data = sorted(calls.most_common(),
728
+ key=lambda x: (-x[1], x[0]))
729
+ msg = '\n'.join(fmt.format(n, f) for f, n in data)
730
+ log.debug(call_type + ' call counts:\n' + msg)
731
+
732
+ @property
733
+ def license_is_valid(self):
734
+ if not self.started:
735
+ return False
736
+ lcc = self._license_check_count
737
+ self._license_check_count = 0 if lcc == 10000 else lcc + 1
738
+ return lcc != 0 or self.handle.LicenseIsValid()
739
+
740
+ def acquire_license(self):
741
+ if self.connected:
742
+ msg = dedent('''\
743
+ PyTecplot is connected to a running
744
+ instance of Tecplot 360.
745
+ Disconnect before attempting to proceed in batch-mode.''')
746
+ raise TecplotLogicError(msg)
747
+ if not self.started:
748
+ self.start()
749
+ if not self.license_is_valid:
750
+ if not self.handle.AcquireLicense():
751
+ raise TecplotLicenseError('Could not acquire a valid license.')
752
+
753
+ def release_license(self):
754
+ if not self.connected and self.license_is_valid:
755
+ self.handle.ReleaseLicense()
756
+
757
+ @property
758
+ def license_expiration(self):
759
+ self.acquire_license()
760
+ expiration_date = self.handle.LicenseExpirationDate().decode()
761
+ try:
762
+ y,m,d = [int(x) for x in expiration_date.split('-')]
763
+ return date(year=y, month=m, day=d)
764
+ except:
765
+ return expiration_date
766
+
767
+ def start_roaming(self, days):
768
+ self.acquire_license()
769
+ if not self.handle.LicenseStartRoaming(int(days)):
770
+ msg = getattr(self.update_last_message(), 'message', None)
771
+ raise TecplotLicenseError(msg)
772
+ expiration_date = self.license_expiration
773
+ if isinstance(expiration_date, date):
774
+ days_left = (expiration_date - date.today()).days
775
+ msg = dedent('''
776
+ You have successfully checked out a roaming license of
777
+ Tecplot. This will be valid for {} days until
778
+ midnight of {}.'''.format(days_left, expiration_date))
779
+ log.critical(msg)
780
+
781
+ @property
782
+ def license_is_roaming(self):
783
+ self.acquire_license()
784
+ return self.handle.LicenseIsRoaming()
785
+
786
+ def stop_roaming(self, force=False):
787
+ try:
788
+ self.acquire_license()
789
+ if not self.handle.LicenseStopRoaming(False):
790
+ raise TecplotLicenseError
791
+ msg = 'Your Tecplot roaming license has been checked in.'
792
+ log.critical('\n' + msg)
793
+ except TecplotError:
794
+ if force:
795
+ if not self.handle.LicenseStopRoaming(force):
796
+ raise TecplotLicenseError
797
+ msg = 'The local Tecplot roaming license has been cleared.'
798
+ log.critical('\n' + msg)
799
+ else:
800
+ try:
801
+ msg = getattr(self.update_last_message(), 'message', '')
802
+ except:
803
+ msg = ''
804
+ msg += dedent(r'''
805
+ You may try this command to
806
+ clear the local roaming license:
807
+ tecplot.session.stop_roaming(force=True)''')
808
+ raise TecplotLicenseError(msg)
809
+
810
+ @property
811
+ def last_message(self):
812
+ return self._last_message
813
+
814
+ def clear_last_message(self):
815
+ self._last_message = None
816
+
817
+ def log_last_message(self):
818
+ if self.last_message:
819
+ if self.last_message.message:
820
+ log.log(self.last_message.level, self.last_message.message)
821
+
822
+ def update_last_message(self):
823
+ def _cleanup_msg(msg):
824
+ msg = msg.decode('utf-8').splitlines()
825
+ return os.linesep.join([s for s in msg if s.strip()])
826
+ last_message = None
827
+ if self.handle is None:
828
+ return last_message
829
+ msg = self.handle.GetTUAssertErrorMessage()
830
+ if msg:
831
+ last_message = TecUtilConnector.Message(
832
+ level=logging.CRITICAL,
833
+ message=_cleanup_msg(msg))
834
+ self.handle.ClearErrorMessage()
835
+ elif __debug__:
836
+ log_level = {
837
+ MessageBoxType.Error: logging.ERROR,
838
+ MessageBoxType.Warning: logging.WARNING,
839
+ MessageBoxType.Information: logging.INFO}
840
+ msg_ptr = self.tecutil_handle.tecUtilLastErrorMessage()
841
+ if msg_ptr:
842
+ msg_char_p = cast(msg_ptr, c_char_p)
843
+ msg = msg_char_p.value
844
+ if msg:
845
+ self.tecutil_handle.tecUtilStringDealloc(msg_char_p)
846
+ # get message type/level
847
+ mbox_val = self.tecutil_handle.tecUtilLastErrorMessageType()
848
+ mbox_type = MessageBoxType(mbox_val)
849
+ last_message = TecUtilConnector.Message(
850
+ level=log_level.get(mbox_type, logging.WARNING),
851
+ message=_cleanup_msg(msg))
852
+ # clean up
853
+ self.tecutil_handle.tecUtilParentLockStart(False)
854
+ try:
855
+ self.tecutil_handle.tecUtilLastErrorMessageClear()
856
+ finally:
857
+ self.tecutil_handle.tecUtilParentLockFinish()
858
+ if last_message:
859
+ self._last_message = last_message
860
+ return last_message
861
+
862
+ @property
863
+ def sdk_version_info(self):
864
+ if not hasattr(self, '_sdk_version_info'):
865
+ try:
866
+ if self.connected:
867
+ self._sdk_version_info = SDKVersion(*self.client.sdk_version_info)
868
+ else:
869
+ self._sdk_version_info = SDKVersion(
870
+ self.tecutil_handle.tecUtilTecplotGetMajorVersion(),
871
+ self.tecutil_handle.tecUtilTecplotGetMinorVersion(),
872
+ self.tecutil_handle.tecUtilTecplotGetMajorRevision(),
873
+ self.tecutil_handle.tecUtilTecplotGetMinorRevision())
874
+ except AttributeError:
875
+ self._sdk_version_info = SDKVersion(0, 0, 0, 0)
876
+ return self._sdk_version_info
877
+
878
+ @property
879
+ def sdk_version(self):
880
+ version_info = self.sdk_version_info
881
+ if version_info == SDKVersion(0, 0, 0, 0):
882
+ return 'unknown'
883
+ else:
884
+ return '{}.{}-{}-{}'.format(*version_info)
885
+
886
+ def update_sdk_version(self):
887
+ import tecplot
888
+ try:
889
+ del self._sdk_version_info
890
+ except AttributeError:
891
+ pass
892
+ tecplot.version.sdk_version_info = self.sdk_version_info
893
+ tecplot.version.sdk_version = self.sdk_version
894
+ tecplot.sdk_version_info = tecplot.version.sdk_version_info
895
+ tecplot.sdk_version = tecplot.version.sdk_version
896
+
897
+ def macro_record_start(self, filename):
898
+ self.start()
899
+ if not self.handle.MacroRecordStart(filename.encode()):
900
+ lastmsg = self.update_last_message()
901
+ if lastmsg:
902
+ if lastmsg.level < logging.ERROR:
903
+ if __debug__:
904
+ self.log_last_message()
905
+ pass
906
+ else:
907
+ raise TecplotSystemError(lastmsg.message)
908
+ else:
909
+ raise TecplotRuntimeError('failed to start macro recording')
910
+
911
+ def macro_record_end(self):
912
+ self.handle.MacroRecordEnd()
913
+
914
+ def translate_macro_to_python(self, command):
915
+ self.start()
916
+
917
+ command = command.strip()
918
+ rawdata = bool(re.search('RAWDATA', command, flags=re.I))
919
+ translate = {
920
+ True: self.handle.TranslateMacroWithRawDataToPython,
921
+ False: self.handle.TranslateMacroToPython,
922
+ }[rawdata]
923
+
924
+ cptr = translate(command.encode('utf-8'))
925
+ cstr = cast(cptr, c_char_p)
926
+ try:
927
+ return cstr.value.decode('utf-8').strip()
928
+ except:
929
+ if "'''" in command:
930
+ command = command.replace("'", r"\'")
931
+ return "tp.macro.execute_command('''" + command + "''')"
932
+ finally:
933
+ self.tecutil_handle.tecUtilStringDealloc(cstr)
934
+
935
+
936
+ _tecutil_connector = TecUtilConnector()
937
+ _tecutil = TecUtil(_tecutil_connector)