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,814 @@
1
+ import itertools as it
2
+ import logging
3
+
4
+ from collections import deque, namedtuple
5
+ from contextlib import contextmanager
6
+ from ctypes import c_int32, c_int64, c_void_p, POINTER, addressof, cast
7
+ from textwrap import dedent
8
+
9
+ from ..tecutil import _tecutil
10
+ from ..constant import *
11
+ from ..exception import *
12
+ from .. import session, tecutil, version
13
+
14
+ log = logging.getLogger(__name__)
15
+
16
+
17
+ @tecutil.lock_attributes
18
+ class Elementmap(c_void_p):
19
+ """Facemap reverse look-up."""
20
+ def __init__(self, zone):
21
+ self.zone = zone
22
+ super().__init__(self._native_reference())
23
+
24
+ @tecutil.lock()
25
+ def _native_reference(self):
26
+ with self.zone.dataset.frame.activated():
27
+ return _tecutil.DataElemGetReadableRef(self.zone.index + 1)
28
+
29
+ def num_faces(self, element):
30
+ return _tecutil.DataElemGetNumFaces(self, element + 1)
31
+
32
+ def face(self, element, offset):
33
+ return _tecutil.DataElemGetFace(self, element + 1, offset + 1) - 1
34
+
35
+ def faces(self, element):
36
+ return [self.face(element, f) for f in range(self.num_faces(element))]
37
+
38
+
39
+ @tecutil.lock_attributes
40
+ class Facemap(c_void_p):
41
+ r"""Connectivity list definition and control.
42
+
43
+ A facemap holds the connectivity for a polytopal finite-element zone. This
44
+ includes node-to-element and element-to-element connections. The following
45
+ script creates a quad of two triangles from scratch using the PyTecplot
46
+ low-level data creation interface. The data created looks like this:
47
+
48
+ .. code-block:: none
49
+
50
+ Node positions (x,y,z):
51
+
52
+ (1,1,1)
53
+ 3
54
+ / \
55
+ / \
56
+ (0,1,.5) 2-----1 (1,0,.5)
57
+ \ /
58
+ \ /
59
+ 0
60
+ (0,0,0)
61
+
62
+ Element indices are used when identifying the left and right of each face,
63
+ where :math:`-1` is used to indicate no element:
64
+
65
+ .. code-block:: none
66
+
67
+ *
68
+ -1 / \ -1
69
+ / 1 \
70
+ *-----*
71
+ \ 0 /
72
+ -1 \ / -1
73
+ *
74
+
75
+ .. code-block:: python
76
+ :emphasize-lines: 28
77
+
78
+ import tecplot as tp
79
+ from tecplot.constant import *
80
+
81
+ nodes = ((0, 0, 0 ),
82
+ (1, 0, 0.5),
83
+ (0, 1, 0.5),
84
+ (1, 1, 1 ))
85
+ faces = ((0, 1),
86
+ (1, 2),
87
+ (2, 0),
88
+ (1, 3),
89
+ (3, 2))
90
+ elements = (( 0, 0, 0, 1, 1), # elements to the left of each face
91
+ (-1, 1, -1, -1, -1)) # elements to the right of each face
92
+ num_elements = 2
93
+ scalar_data = (0, 1, 2, 3)
94
+
95
+ ds = tp.active_frame().create_dataset('Data', ['x','y','z','s'])
96
+ z = ds.add_poly_zone(ZoneType.FEPolygon,
97
+ name='FE Polygon Float (4,2,5) Nodal',
98
+ num_points=len(nodes),
99
+ num_elements=num_elements,
100
+ num_faces=len(faces))
101
+
102
+ z.values('x')[:] = [n[0] for n in nodes]
103
+ z.values('y')[:] = [n[1] for n in nodes]
104
+ z.values('z')[:] = [n[2] for n in nodes]
105
+ z.facemap.set_mapping(faces, elements)
106
+ z.values('s')[:] = scalar_data
107
+
108
+ ### setup a view of the data
109
+ plot = tp.active_frame().plot(PlotType.Cartesian3D)
110
+ plot.activate()
111
+
112
+ cont = plot.contour(0)
113
+ cont.colormap_name = 'Sequential - Yellow/Green/Blue'
114
+ cont.colormap_filter.distribution = ColorMapDistribution.Continuous
115
+
116
+ for ax in plot.axes:
117
+ ax.show = True
118
+
119
+ plot.show_mesh = False
120
+ plot.show_contour = True
121
+ plot.show_edge = True
122
+ plot.use_translucency = True
123
+
124
+ fmap = plot.fieldmap(z)
125
+ fmap.surfaces.surfaces_to_plot = SurfacesToPlot.All
126
+ fmap.effects.surface_translucency = 40
127
+
128
+ # View parameters obtained interactively from Tecplot 360
129
+ plot.view.distance = 10
130
+ plot.view.width = 2
131
+ plot.view.psi = 80
132
+ plot.view.theta = 30
133
+ plot.view.alpha = 0
134
+ plot.view.position = (-4.2, -8.0, 2.3)
135
+
136
+ # Turning on mesh, we can see all the individual triangles
137
+ plot.show_mesh = True
138
+ plot.fieldmap(z).mesh.line_pattern = LinePattern.Dashed
139
+
140
+ cont.levels.reset_to_nice()
141
+ tp.export.save_png('polygons1.png', 600, supersample=3)
142
+
143
+ .. figure:: /_static/images/polygons1.png
144
+ :width: 300px
145
+ :figwidth: 300px
146
+
147
+ Two triangle polygons showing edge and mesh lines.
148
+ """
149
+ def __init__(self, zone):
150
+ self.zone = zone
151
+ if self.zone.num_faces > 0:
152
+ try:
153
+ ref = self._native_reference()
154
+ except:
155
+ ref = None
156
+ else:
157
+ ref = None
158
+ super().__init__(ref)
159
+
160
+ @property
161
+ def _has_data_backing(self):
162
+ return bool(self)
163
+
164
+ @tecutil.lock()
165
+ def _native_reference(self, writable=False):
166
+ try:
167
+ _dispatch = {
168
+ True: _tecutil.DataFaceMapGetWritableRef,
169
+ False: _tecutil.DataFaceMapGetReadableRef}
170
+ with self.zone.dataset.frame.activated():
171
+ return _dispatch[writable](self.zone.index + 1)
172
+ except TecplotLogicError:
173
+ raise TecplotLogicError(
174
+ 'A facemap must first be created with Facemap.alloc()')
175
+
176
+ @tecutil.lock()
177
+ def alloc(self, face_nodes, boundary_faces=0, boundary_connections=0):
178
+ """Allocate space for the facemap.
179
+
180
+ Parameters:
181
+ face_nodes (`int`): Total number of nodes for all faces. This is
182
+ not the number of unique nodes but the total number. For
183
+ example if a facemap defines two triangle polygons that share a
184
+ common face, ``faces`` would be 5 and ``face_nodes`` would be
185
+ 6, not 4.
186
+ boundary_faces (`int`, optional): Total number of boundary faces.
187
+ (default: 0)
188
+ boundary_connections (`int`, optional): Total number of boundary
189
+ face elements or boundary face element/zone pairs. (default: 0)
190
+
191
+ Returns:
192
+ `Facemap`
193
+
194
+ This is called when using the `Facemap.set_mapping()` method which is
195
+ the preferred method for filling the connectivity of polytope zones. If
196
+ the zone does not already have space allocated for a facemap and if you
197
+ wish to use the `Facemap.set_nodes()` and `Facemap.set_elements()`
198
+ methods to fill in the connectivity, then this must be called first::
199
+
200
+ >>> facemap = zone.facemap.alloc(400, 25, 50)
201
+
202
+ .. note:: Tecplot version 2017.2 or later.
203
+
204
+ Setting the boundary faces and boundary connections using PyTecplot
205
+ requires Tecplot version 2017.2 or later.
206
+ """
207
+ if not _tecutil.DataFaceMapAlloc(self.zone.index + 1,
208
+ self.zone.num_faces, face_nodes,
209
+ boundary_faces, boundary_connections):
210
+ raise TecplotSystemError()
211
+ c_void_p.__init__(self, self._native_reference())
212
+
213
+ def __eq__(self, other):
214
+ if not (self._has_data_backing and other._has_data_backing):
215
+ return False
216
+ self_addr = addressof(cast(self, POINTER(c_int64)).contents)
217
+ other_addr = addressof(cast(other, POINTER(c_int64)).contents)
218
+ return self_addr == other_addr
219
+
220
+ def __ne__(self, other):
221
+ return not (self == other)
222
+
223
+ def __len__(self):
224
+ return self.num_faces()
225
+
226
+ def num_faces(self, element=None):
227
+ """The number of faces of an element in this `Facemap`.
228
+
229
+ Parameters:
230
+ element (`int`, optional): Zero-based index of an
231
+ element. If no element is given, the total number of faces
232
+ in the map are returned.
233
+
234
+ Returns:
235
+ `int`
236
+
237
+ Example usage::
238
+
239
+ >>> print(zone.facemap.num_faces())
240
+ 1048576
241
+ """
242
+ if element is None:
243
+ if not self._has_data_backing:
244
+ return self.zone.num_faces
245
+ return _tecutil.DataFaceMapGetNFaces(self)
246
+ else:
247
+ return Elementmap(self.zone).num_faces(element)
248
+
249
+ @property
250
+ def num_unique_nodes(self):
251
+ """`int`: The number of unique nodes in this `Facemap`.
252
+
253
+ .. note:: This property is read-only.
254
+
255
+ Example usage::
256
+
257
+ >>> print(zone.facemap.num_unique_nodes)
258
+ 4194304
259
+ """
260
+ if not self._has_data_backing:
261
+ return self.zone.num_points
262
+ return _tecutil.DataFaceMapGetNumNodes(self)
263
+
264
+ def num_nodes(self, face=None, element=None):
265
+ """The number nodes for a given face in this `Facemap`.
266
+
267
+ Parameters:
268
+ face (`int`, required if no *element* is given): The
269
+ zero-based index of the face either within the given *element*
270
+ or globally. If no face is given, the number of nodes on the
271
+ given *element* are returned.
272
+ element (`int`, required if no face is given): The
273
+ zero-based index of an element. If no element is given, then
274
+ *face* is globally indexed from zero over the whole zone.
275
+
276
+ Returns:
277
+ `int` The number of nodes.
278
+
279
+ Example usage::
280
+
281
+ >>> print(zone.facemap.num_nodes(face=1))
282
+ 4
283
+ """
284
+ if element is None:
285
+ if face is None:
286
+ msg = 'face or element must be specified'
287
+ raise TecplotValueError(msg)
288
+ # While the following sum() is technically correct, it is
289
+ # extremely slow as currently, there is no way to query
290
+ # the total number of nodes in the connectivity list directly.
291
+ #
292
+ # return sum(self.num_nodes(i) for i in range(self.num_faces()))
293
+ else:
294
+ if self.zone.zone_type == ZoneType.FEPolygon:
295
+ return 2
296
+ else:
297
+ return _tecutil.DataFaceMapGetNFaceNodes(self, face + 1)
298
+ else:
299
+ emap = Elementmap(self.zone)
300
+ if face is None:
301
+ return sum(self.num_nodes(f) for f in emap.faces(element))
302
+ else:
303
+ return self.num_nodes(emap.face(element, face))
304
+
305
+ def num_boundary_connections(self, face=None, element=None):
306
+ """The number of boundary connections for a given face.
307
+
308
+ Parameters:
309
+ face (`int`, required if no element is given): The
310
+ zero-based index of the face.
311
+ element (`int`, optional): Zero-based index of an
312
+ element. If given, *face* will be locally indexed within this
313
+ element, otherwise *face* is globally indexed over the whole
314
+ zone.
315
+
316
+ Returns:
317
+ `int` The number of boundary connections.
318
+
319
+ Example usage::
320
+
321
+ >>> print(zone.facemap.num_boundary_connections(1))
322
+ 1
323
+ """
324
+ if face is None:
325
+ if element is None:
326
+ msg = 'face or element must be specified'
327
+ raise TecplotValueError(msg)
328
+ # While the following sum() is technically correct using
329
+ # the face indices here, it is extremely slow as currently,
330
+ # there is no way to query the total number of boundary
331
+ # connections directly.
332
+ #
333
+ # faces = range(self.num_faces())
334
+ else:
335
+ emap = Elementmap(self.zone)
336
+ faces = emap.faces(element)
337
+ return sum(self.num_boundary_connections(f) for f in faces)
338
+ elif element is not None:
339
+ face = self.face(element, face)
340
+ return _tecutil.DataFaceMapGetNBndryConns(self, face + 1)
341
+
342
+ _BoundaryConnection = namedtuple('BoundaryConnection', ['element', 'zone'])
343
+
344
+ def boundary_connection(self, face, offset, element=None):
345
+ """The connected element and zone along a boundary face.
346
+
347
+ Parameters:
348
+ face (`int`): The zero-based index of the face.
349
+ offset (`int`): The zero-based index of the node being
350
+ requested.
351
+ element (`int`, optional): Zero-based index of an
352
+ element. If given, *face* will be locally indexed within this
353
+ element, otherwise *face* is globally indexed over the whole
354
+ zone.
355
+
356
+ Returns:
357
+ `namedtuple <collections.namedtuple>`: ``(element, zone)``:
358
+ ``element``
359
+ The zero-based index of the neighboring element.
360
+ ``zone``
361
+ The zone holding the neighboring element.
362
+
363
+ Example usage::
364
+
365
+ >>> bconn = zone.facemap.boundary_connection(0, 2)
366
+ >>> print(bconn.element)
367
+ 128
368
+ >>> print(bconn.zone.index)
369
+ 2
370
+ """
371
+ if element is not None:
372
+ face = self.face(element, face)
373
+ elem, zone = _tecutil.DataFaceMapGetBndryConn(self, face + 1,
374
+ offset + 1)
375
+ return Facemap._BoundaryConnection(elem - 1,
376
+ self.zone.dataset.zone(zone - 1))
377
+
378
+ def node(self, face, offset, element=None):
379
+ """The node index along a specific face.
380
+
381
+ Parameters:
382
+ face (`int`): The zero-based index of the face.
383
+ offset (`int`): The zero-based index of the node being
384
+ requested.
385
+ element (`int`, optional): Zero-based index of an
386
+ element. If given, *face* will be locally indexed within this
387
+ element, otherwise *face* is globally indexed over the whole
388
+ zone.
389
+
390
+ Returns:
391
+ `int` The node at the specified location.
392
+
393
+ Example usage::
394
+
395
+ >>> print(zone.facemap.face_node(0, 2))
396
+ 128
397
+ """
398
+ if element is not None:
399
+ face = self.face(element, face)
400
+ return _tecutil.DataFaceMapGetFaceNode(self, face + 1, offset + 1) - 1
401
+
402
+ def left_element(self, face, element=None):
403
+ """The element to the left of a specific face.
404
+
405
+ Parameters:
406
+ face (`int`): The zero-based index of the face.
407
+ element (`int`, optional): Zero-based index of an
408
+ element. If given, *face* will be locally indexed within this
409
+ element, otherwise *face* is globally indexed over the whole
410
+ zone.
411
+
412
+ Returns:
413
+ `int`
414
+
415
+ A negative number indicates there is no element to the left of this
416
+ face. Example usage::
417
+
418
+ >>> print(zone.facemap.left_element(0))
419
+ 128
420
+ """
421
+ if element is not None:
422
+ face = self.face(element, face)
423
+ return _tecutil.DataFaceMapGetLeftElem(self, face + 1) - 1
424
+
425
+ def right_element(self, face, element=None):
426
+ """The element to the right of a specific face.
427
+
428
+ Parameters:
429
+ face (`int`): The zero-based index of the face.
430
+ element (`int`, optional): Zero-based index of an
431
+ element. If given, *face* will be locally indexed within this
432
+ element, otherwise *face* is globally indexed over the whole
433
+ zone.
434
+
435
+ Returns:
436
+ `int`
437
+
438
+ A negative number indicates there is no element to the right of this
439
+ face. Example usage::
440
+
441
+ >>> print(zone.facemap.right_element(0))
442
+ -1
443
+ """
444
+ if element is not None:
445
+ face = self.face(element, face)
446
+ return _tecutil.DataFaceMapGetRightElem(self, face + 1) - 1
447
+
448
+ def face(self, element, offset):
449
+ """Face index on a specific element.
450
+
451
+ Parameters:
452
+ element (`int`): The zero-based index of the element.
453
+ offset (`int`): The zero-based index of the face being
454
+ requested.
455
+
456
+ Returns:
457
+ `int` Zero-based index of the face at the specified
458
+ location.
459
+
460
+ Example usage::
461
+
462
+ >>> print(zone.facemap.face(0, 2))
463
+ 128
464
+ """
465
+ return Elementmap(self.zone).face(element, offset)
466
+
467
+ @property
468
+ def node_c_type(self):
469
+ """The data type of the node indices.
470
+
471
+ Possible values: `ctypes.c_int32`, `ctypes.c_int64`
472
+
473
+ .. note:: This property is read-only.
474
+ """
475
+ _ctypes = {
476
+ OffsetDataType.OffsetDataType_32Bit: c_int32,
477
+ OffsetDataType.OffsetDataType_64Bit: c_int64}
478
+ if self._has_data_backing:
479
+ data_type = _tecutil.DataFaceMapGetNodeRawItemType(self)
480
+ return _ctypes[data_type]
481
+
482
+ @property
483
+ def element_c_type(self):
484
+ """The data type of the element indices.
485
+
486
+ Possible values: `ctypes.c_int32`, `ctypes.c_int64`
487
+
488
+ .. note:: This property is read-only.
489
+ """
490
+ _ctypes = {
491
+ OffsetDataType.OffsetDataType_32Bit: c_int32,
492
+ OffsetDataType.OffsetDataType_64Bit: c_int64}
493
+ if self._has_data_backing:
494
+ ref = self._native_reference(writable=True)
495
+ data_type = _tecutil.DataFaceMapGetElementRawItemType(ref)
496
+ return _ctypes[data_type]
497
+
498
+ def set_nodes(self, facemap):
499
+ """Sets the polytope connectivity.
500
+
501
+ Parameters:
502
+ facemap (2D array of zero-based `integers <int>`): The `list` of
503
+ `lists <list>` which need not all be the same length, defining
504
+ the individual elements by their nodes.
505
+
506
+ The facemap must first be allocated with `Facemap.alloc()` and must be
507
+ called from within a `Facemap.assignment()` context and should be
508
+ followed by a call to `Facemap.set_elements()` to complete the
509
+ connectivity map information needed for rendering. It is recomended to
510
+ use the `Facemap.set_mapping()` which does all the required
511
+ book keeping.
512
+ """
513
+ _dispatch = {
514
+ c_int32: _tecutil.DataFaceMapAssignNodes,
515
+ c_int64: _tecutil.DataFaceMapAssignNodes64}
516
+ data_type = self.node_c_type
517
+ nfaces = len(facemap)
518
+ nnodes = [len(n) for n in facemap]
519
+ nfacenodes = sum(nnodes)
520
+ if self.zone.zone_type is ZoneType.FEPolygon:
521
+ faces = None
522
+ else:
523
+ faces = (c_int32 * nfaces)(*nnodes)
524
+ nodes = (data_type * nfacenodes)(*[n + 1 for n in it.chain(*facemap)])
525
+
526
+ if __debug__:
527
+ if min(nodes) < 1 or self.num_unique_nodes < max(nodes):
528
+ raise TecplotIndexError
529
+ log.debug(dedent('''\
530
+ Facemap set nodes:
531
+ faces({}): {}
532
+ nodes: {}''').format(nfaces, tecutil.array_to_str(faces),
533
+ tecutil.array_to_str(nodes)))
534
+
535
+ ref = self._native_reference(writable=True)
536
+ _dispatch[data_type](ref, nfaces, faces, nodes)
537
+
538
+ def set_elements(self, left_elements, right_elements):
539
+ """Sets the polytope connectivity.
540
+
541
+ Parameters:
542
+ left_elements (array of zero-based `integers <int>`): This is an
543
+ array of the elements to the left of each face in the facemap
544
+ and must be the same length as the number of faces.
545
+ right_elements (array of zero-based `integers <int>`): Same as
546
+ *left_elements* for the right side of each face.
547
+
548
+ The facemap must first be allocated with `Facemap.alloc()` and must be
549
+ called from within a `Facemap.assignment()` context and should follow a
550
+ call to `Facemap.set_nodes()` to complete the connectivity map
551
+ information needed for rendering. Using `Facemap.set_mapping()` is
552
+ recommended, which does all the required book keeping.
553
+ """
554
+ _dispatch = {
555
+ c_int32: _tecutil.DataFaceMapAssignElems,
556
+ c_int64: _tecutil.DataFaceMapAssignElems64}
557
+ data_type = self.element_c_type
558
+ nelements = len(left_elements)
559
+ left = (data_type * nelements)(*[e + 1 for e in left_elements])
560
+ right = (data_type * nelements)(*[e + 1 for e in right_elements])
561
+
562
+ if __debug__:
563
+ if self.zone.num_elements < max(left):
564
+ raise TecplotIndexError
565
+ if self.zone.num_elements < max(right):
566
+ raise TecplotIndexError
567
+ log.debug(dedent('''\
568
+ Facemap set elements:
569
+ left({}): {}
570
+ right({}): {}'''
571
+ ).format(len(left), tecutil.array_to_str(left),
572
+ len(right), tecutil.array_to_str(right)))
573
+
574
+ ref = self._native_reference(writable=True)
575
+ _dispatch[data_type](ref, nelements, left, right)
576
+
577
+ def set_boundary_connections(self, elements, zones):
578
+ """Set the boundary connections.
579
+
580
+ Parameters:
581
+ elements (2D array of `integers <int>`): Zero-based indices of the
582
+ connected elements. This is a "ragged" array of dimension
583
+ :math:`(N,E_i)` where :math:`N` is the number of boundary faces
584
+ and :math:`E_i` is the number of boundary connected elements
585
+ for the :math:`i^{th}` face.
586
+ zones (2D array of `integers <int>`): Zero-based indices of the
587
+ zones for each entry given in *elements*. This must be the same
588
+ shape as *elements*.
589
+
590
+ The facemap must first be allocated with `Facemap.alloc()` and must be
591
+ called from within a `Facemap.assignment()` context, and should follow
592
+ calls to `Facemap.set_nodes()` and `Facemap.set_elements()` to complete
593
+ the connectivity map information needed for rendering. Using
594
+ `Facemap.set_mapping()` is recommended, which does all the required
595
+ book keeping.
596
+ """
597
+ _dispatch = {
598
+ c_int32: _tecutil.DataFaceMapAssignBConns,
599
+ c_int64: _tecutil.DataFaceMapAssignBConns64}
600
+ data_type = self.node_c_type
601
+ nbfaces = len(elements)
602
+ nbconns = (c_int32 * nbfaces)(*[len(n) for n in elements])
603
+ nbelems = sum(nbconns)
604
+
605
+ def _flatten_add_one(arr):
606
+ def _f(x):
607
+ return x + 1 if x is not None else 0
608
+ return list(map(_f, it.chain(*arr)))
609
+
610
+ def _flatten_add_one_index(arr):
611
+ def _f(x):
612
+ return getattr(x, 'index', x) + 1 if x is not None else 0
613
+ return list(map(_f, it.chain(*arr)))
614
+
615
+ belems = (data_type * nbelems)(*_flatten_add_one(elements))
616
+ bzones = (c_int32 * nbelems)(*_flatten_add_one_index(zones))
617
+
618
+ if __debug__:
619
+ if min(belems) < 1 or self.zone.num_elements < max(belems):
620
+ raise TecplotIndexError
621
+ if min(bzones) < 1 or self.zone.dataset.num_zones < max(bzones):
622
+ raise TecplotIndexError
623
+ log.debug(dedent('''\
624
+ Facemap set boundary connections:
625
+ num boundary faces: {}
626
+ num boundary conns: {}
627
+ boundary elems: {}
628
+ boundary zones: {}'''.format(nbfaces,
629
+ tecutil.array_to_str(nbconns),
630
+ tecutil.array_to_str(belems),
631
+ tecutil.array_to_str(bzones))))
632
+
633
+ if version.sdk_version_info >= (2021, 2):
634
+ # The TecUtil API 2021.2+ requires boundary zone unique IDs.
635
+ bzone_uids = (c_int64 * nbelems)()
636
+ _tecutil.DataSetGetUniqueIDsForZones(bzones, nbelems, bzone_uids)
637
+ bzones = bzone_uids
638
+
639
+ ref = self._native_reference(writable=True)
640
+ _dispatch[data_type](ref, nbfaces, nbconns, belems, bzones)
641
+
642
+ def set_mapping(self, facemap, elements, boundary_elements=None,
643
+ boundary_zones=None):
644
+ """Set the node and element connectivity for this polytope zone.
645
+
646
+ Parameters:
647
+ facemap (2D array of zero-based `integers <int>`): The `list` of
648
+ `lists <list>` which need not all be the same length, defining
649
+ the individual elements by their nodes.
650
+ elements (2D array of zero-based `integers <int>`): This is a
651
+ :math:`(2,N)` array where :math:`N` is the number of faces and
652
+ the items are a list of the elements to the left and right of
653
+ the face respectively.
654
+ boundary_elements (2D array of `integers <int>`, optional):
655
+ Zero-based indices of the connected elements. This is a
656
+ "ragged" array of dimension :math:`(N, E_i)` where :math:`N` is
657
+ the number of boundary faces and :math:`E_i` is the number of
658
+ boundary connected elements for the :math:`i^{th}` face.
659
+ boundary_zones (2D array of `integers <int>`, optional): Zero-based
660
+ indices of the zones for each entry given in *elements*. This
661
+ must be the same shape as *elements*.
662
+
663
+ See the code example for the `Facemap` class object for details.
664
+
665
+ .. note:: **boundary_elements** and **boundary_zones**
666
+
667
+ The two parameters, **boundary_elements** and **boundary_zones**,
668
+ must be supplied together or not at all.
669
+ """
670
+ nfacenodes = sum(len(n) for n in facemap)
671
+ if boundary_elements is not None:
672
+ nbfaces = len(boundary_elements)
673
+ nbelems = sum(len(n) for n in boundary_elements)
674
+ else:
675
+ nbfaces = 0
676
+ nbelems = 0
677
+
678
+ if __debug__:
679
+ log.debug(dedent('''\
680
+ Facemap alloc:
681
+ num face nodes: {}
682
+ num boundary faces: {}
683
+ num boundary elements: {}'''.format(nfacenodes, nbfaces,
684
+ nbelems)))
685
+
686
+ self.alloc(nfacenodes, nbfaces, nbelems)
687
+ with self.assignment():
688
+ self.set_nodes(facemap)
689
+ self.set_elements(*elements)
690
+ if boundary_elements is not None:
691
+ self.set_boundary_connections(boundary_elements, boundary_zones)
692
+
693
+ @contextmanager
694
+ def assignment(self):
695
+ """Context manager for assigning facemap connections.
696
+
697
+ This context ensures the proper book keeping is done when setting the
698
+ connectivity list and must be used with `Facemap.set_nodes()`,
699
+ `Facemap.set_elements()` and `Facemap.set_boundary_connections()`,
700
+ which are used to define the connectivity of the zone.
701
+ """
702
+ if hasattr(self, '_in_assignment'):
703
+ yield
704
+ else:
705
+ with tecutil.lock():
706
+ ref = self._native_reference(writable=True)
707
+ _tecutil.DataFaceMapBeginAssign(ref)
708
+ try:
709
+ self._in_assignment = True
710
+ yield
711
+ finally:
712
+ try:
713
+ if not _tecutil.DataFaceMapEndAssign(ref):
714
+ raise TecplotSystemError()
715
+ finally:
716
+ del self._in_assignment
717
+
718
+ def set_elementmap(self, elementmap):
719
+ """Define connectivity per element.
720
+
721
+ Parameters:
722
+ elementmap (`integers <int>`): Zero-based indices of the nodes that
723
+ make up each face of each element. For polygons, the map is a
724
+ list of elements, each made of up a list of nodes. For
725
+ polyhedrons, this is a list of elements, made up a list of
726
+ faces, each made up of a list of nodes.
727
+
728
+ .. warning::
729
+
730
+ This method is mutually exclusive with the `Facemap.set_mapping()`
731
+ and `Facemap.assignment()`, `Facemap.set_nodes()`,
732
+ `Facemap.set_elements()` and `Facemap.set_boundary_connections()`
733
+ family of methods. The size of the underlying arrays are calculated
734
+ based on the elementmap given and a call to `Facemap.alloc()` is
735
+ made which will override any previous allocation.
736
+
737
+ This may be a more convenient way to describe the connectivity of a
738
+ polytope zone, however it does not support boundary face connections to
739
+ other zones (see `Facemap.set_mapping()`).
740
+
741
+ Here is an example of an elementmap for two triangles (polygons)::
742
+
743
+ nodes = ((0, 0, 0 ),
744
+ (1, 0, 0.5),
745
+ (0, 1, 0.5),
746
+ (1, 1, 1 ))
747
+ elementmap = ((0, 1, 2), # polygon 0, 3 faces
748
+ (1, 3, 2)) # polygon 1, 3 faces
749
+
750
+ This is an example of an elementmap for two tetrahedrons
751
+ (polyhedrons)::
752
+
753
+ nodes = ((0, 0, 0),
754
+ (1, 1, 0),
755
+ (1, 0, 1),
756
+ (0, 1, 1),
757
+ (0, 0, 1))
758
+ elementmap = (((0, 1, 2), # polyhedron 0, 4 faces
759
+ (0, 1, 3),
760
+ (1, 3, 2),
761
+ (0, 2, 3)),
762
+ ((0, 2, 3), # polyhedron 1, 4 faces
763
+ (2, 3, 4),
764
+ (0, 2, 4),
765
+ (0, 4, 3)))
766
+
767
+ """
768
+ _dispatch = {
769
+ c_int32: _tecutil.DataFaceMapAssignElemToNodeMap,
770
+ c_int64: _tecutil.DataFaceMapAssignElemToNodeMap64}
771
+ data_type = self.node_c_type
772
+ if data_type is None:
773
+ data_type = c_int32
774
+
775
+ nelems = len(elementmap)
776
+
777
+ faces = [len(e) for e in elementmap]
778
+ faces = (c_int32 * len(faces))(*faces)
779
+
780
+ if self.zone.zone_type == ZoneType.FEPolygon:
781
+ nodes = None
782
+ elemmap = [n + 1 for face in elementmap for n in face]
783
+ else: # if self.zone.zone_type == ZoneType.FEPolyhedron:
784
+ nodes = [len(face) for elem in elementmap for face in elem]
785
+ nodes = (c_int32 * len(nodes))(*nodes)
786
+ elemmap = [n + 1 for elem in elementmap
787
+ for face in elem
788
+ for n in face]
789
+
790
+ elemmap = (data_type * len(elemmap))(*elemmap)
791
+
792
+ if __debug__:
793
+ if len(elemmap):
794
+ if min(elemmap) < 1 or self.num_unique_nodes < max(elemmap):
795
+ raise TecplotIndexError
796
+ log.debug(dedent('''\
797
+ FaceMapAssign:
798
+ nelems: {}
799
+ faces: {}
800
+ nodes: {}
801
+ elemmap: {}''').format(nelems, tecutil.array_to_str(faces),
802
+ tecutil.array_to_str(nodes),
803
+ tecutil.array_to_str(elemmap)))
804
+
805
+ # reset facemap before we fill it with the nodemap
806
+ if self.zone.zone_type == ZoneType.FEPolygon:
807
+ self.alloc(int(self.zone.num_faces * self.num_nodes(0)))
808
+ else:
809
+ self.alloc(0)
810
+
811
+ with tecutil.lock():
812
+ ref = self._native_reference(writable=True)
813
+ if not _dispatch[data_type](ref, nelems, faces, nodes, elemmap):
814
+ raise TecplotSystemError()