import fnmatch
import logging
import re
import warnings
from contextlib import contextmanager
from ..tecutil import _tecutil
from ..constant import *
from ..exception import *
from .. import macro, session, tecutil
from ..tecutil import Index, lock, lock_attributes, sv
from .frame import Frame
log = logging.getLogger(__name__)
[docs]
@lock_attributes
class Page(object):
"""`Page <tecplot.layout.Page>` object within a layout, holding onto one or more `Frames <tecplot.layout.Frame>`.
Parameters:
uid (`int`, optional): This must be a *valid* unique ID
number pointing internally to a `Page <tecplot.layout.Page>` object or `None`. A new
`Page <tecplot.layout.Page>` is created if set to `None`. (default: `None`)
Warning:
Though it is possible to create a `Page <tecplot.layout.Page>` object using the
constructor, it is usually sufficient to obtain a page through
`tecplot.add_page`, `tecplot.active_page`, `tecplot.page` or
`tecplot.pages`.
A `Page <tecplot.layout.Page>` can be thought of like a canvas onto which one or more
`Frames <tecplot.layout.Frame>` can be laid out. The engine guarantees there will
always be at least one `Page <tecplot.layout.Page>` in the layout which can be accessed
via `tecplot.active_page`:
.. code-block:: python
import tecplot
page = tecplot.active_page()
page.name = 'Page 001'
# prints: "Page 001"
print(page.name)
# prints: "Frame 001"
for frame in page.frames():
print(frame.name)
"""
def __init__(self, uid):
self.uid = uid
self.framelist = None
"""The unique ID number of this Page, internal to the |Tecplot Engine|."""
self._sv = [sv.PAGE]
def __str__(self):
"""Brief string representation.
Returns:
`str`: Brief representation of this `Page <tecplot.layout.Page>`.
Example:
.. code-block:: python
import tecplot
page = tecplot.active_page()
page.name = 'Page 001'
# will print: 'Page: "Page 001"'
print(page)
"""
return 'Page: "{name}"'.format(name=self.name)
def __repr__(self):
"""Executable string representation.
Returns:
`str`: Internal representation of this `Page <tecplot.layout.Page>`.
The string returned can be executed to generate an identical
copy of this `Page <tecplot.layout.Page>` object:
.. code-block:: python
import tecplot
from tecplot.layout import Page
page = tecplot.active_page()
'''
The "repr" string of the Page is executable code.
The following will print: "Page(uid=1)"
'''
print(repr(page))
page2 = None
exec('page2 = '+repr(page))
'''
At this point, page2 is just another handle to
the exact same page object in the Tecplot Engine
'''
assert page2 == page
"""
return 'Page(uid={uid})'.format(uid=self.uid)
def __eq__(self, other):
"""Checks for `Page <tecplot.layout.Page>` equality in the |Tecplot Engine|.
Returns:
`bool`: `True` if the unique ID numbers are the same for both `Pages <tecplot.layout.Page>`.
Example:
.. code-block:: python
import tecplot
page1 = tecplot.active_page()
page2 = tecplot.add_page()
assert page1 != page2
assert tecplot.active_page() == page2
"""
return self.uid == other.uid
@lock()
def __contains__(self, frame):
with self.activated():
result = False
if _tecutil.FrameGetCount():
with session.suspend():
_tecutil.FrameLightweightLoopStart()
try:
while True:
if frame.uid == _tecutil.FrameGetUniqueID():
result = True
break
if not _tecutil.FrameLightweightLoopNext():
break
finally:
_tecutil.FrameLightweightLoopEnd()
return result
def __getitem__(self, pattern):
return self.frame(pattern)
def __iter__(self):
self.framelist = list(self.frames())
return self
def __next__(self):
try:
return self.framelist.pop(0)
except (KeyError, IndexError):
raise StopIteration
@property
def aux_data(self):
"""Auxiliary data for this page.
Returns: `AuxData`
This is the auxiliary data attached to the page. Such data is written
to the layout file by default and can be retrieved later. Example
usage::
>>> aux = tp.active_page().aux_data
>>> aux['Result'] = '3.14159'
>>> print(aux['Result'])
3.14159
"""
return session.AuxData(self, AuxDataObjectType.Page)
@property
def position(self):
"""`Index`: Index of the Page
The page positions are 0 based positions relative to the current page,
where the current page has a position value of 0, the next page 1,
the page after that 2, and so on.
"""
return Index(_tecutil.PageGetPosByUniqueID(self.uid) - 1)
@property
def name(self):
"""`str`: Name of the page.
This is the name used when searching for `Page <tecplot.layout.Page>` objects in
`tecplot.pages` and `tecplot.page`. It does not have to be unique.
Example:
.. code-block:: python
import tecplot
page = tecplot.active_page()
page.name = 'My Data'
# prints: "this page: My Data"
print('this page:', page.name)
"""
with self.activated():
return _tecutil.PageGetName()[1]
@name.setter
@lock()
def name(self, name):
with self.activated():
if not _tecutil.PageSetName(name):
raise TecplotSystemError()
@property
def paper(self):
"""`Paper`: The `Paper` defined in this `Page <tecplot.layout.Page>`.
Every `Page <tecplot.layout.Page>` has the concept of a workspace which includes
all `Frames <tecplot.layout.Frame>` as well as a sub-area of the workspace
called the `Paper`. The limits of the `Paper` with respect to
the placement of `Frames <tecplot.layout.Frame>` is used when exporting
certain image formats.
"""
return Paper(self)
@property
def active(self):
"""Checks if this `Page <tecplot.layout.Page>` is active.
Returns:
`bool`: `True` if active.
"""
return self.uid == _tecutil.PageGetUniqueID()
[docs]
@lock()
def activate(self):
"""Activates the `Page <tecplot.layout.Page>`.
Raises:
`TecplotRuntimeError`: Page does not exist.
`TecplotSystemError`: Could not activate the page.
"""
if not self.active:
if not self.exists:
raise TecplotRuntimeError('page does not exists')
elif not _tecutil.PageSetCurrentByUniqueID(self.uid):
raise TecplotSystemError('could not activate page')
@contextmanager
def activated(self):
current_page = Page(_tecutil.PageGetUniqueID())
if self == current_page:
yield
else:
self.activate()
try:
yield
finally:
current_page.activate()
[docs]
def active_frame(self):
"""Returns the active `Frame <tecplot.layout.Frame>`.
Returns:
`Frame <tecplot.layout.Frame>`: The active `Frame <tecplot.layout.Frame>`.
This implicitly activates this `Page <tecplot.layout.Page>` and returns the active
`Frame <tecplot.layout.Frame>` attached to it.
"""
self.activate()
if _tecutil.FrameGetCount():
return Frame(_tecutil.FrameGetActiveID(), self)
[docs]
@lock()
def add_frame(self, position=None, size=None):
"""Creates a new `Frame <tecplot.layout.Frame>` in this `Page <tecplot.layout.Page>`.
Parameters:
position (`tuple` of `floats <float>` (x,y), optional): The
position (in inches) of the frame relative to the top left
corner of the paper. If supplied, size must also be supplied.
size (`tuple` of `floats <float>` (width,height), optional): The
size (in inches) of the frame. If supplied, position must also
be supplied.
Returns:
`Frame <tecplot.layout.Frame>`: The newly created and activated `Frame <tecplot.layout.Frame>`.
This implicitly activates the `Page <tecplot.layout.Page>` and creates and activates
a new `Frame <tecplot.layout.Frame>`.
.. code-block:: python
import tecplot as tp
frame = tp.active_page().add_frame(position=(1, 0.25), size=(8, 9))
"""
supplied_frame_size = bool(position or size)
if __debug__:
if bool(position) ^ bool(size):
raise TecplotValueError('must supply position and size')
msg = 'position and size must be 2-tuple'
if supplied_frame_size:
if hasattr(position, '__iter__') and hasattr(size, '__iter__'):
if len(position) != 2 or len(size) != 2:
raise TecplotValueError(msg)
else:
raise TecplotTypeError(msg)
pos = tecutil.XY(*(position or (0, 0)))
size = tecutil.XY(*(size or (0, 0)))
self.activate()
if not _tecutil.FrameCreateNew(supplied_frame_size, pos.x, pos.y, size.x, size.y):
raise TecplotSystemError('could not create new frame')
else:
uid = _tecutil.FrameGetActiveID()
if uid > 0:
return Frame(uid, self)
else:
raise TecplotRuntimeError('could not get id of newly created active frame')
[docs]
@lock()
def delete_frame(self, frame):
"""Removes the frame from this `Page <tecplot.layout.Page>`.
Raises:
`TecplotRuntimeError`: If `Frame <tecplot.layout.Frame>` is not in this `Page <tecplot.layout.Page>`.
`TecplotSystemError`: Could not delete the frame.
"""
if frame not in self:
raise TecplotRuntimeError('frame is not in this page')
else:
with frame.activated():
if not _tecutil.FrameDeleteActive():
raise TecplotSystemError()
[docs]
@lock()
def frame(self, pattern):
"""Returns the `Frame <tecplot.layout.Frame>` by name.
Parameters:
pattern (`str` or `re.Pattern <re.compile>`): Case-insensitive
`glob-style pattern string <fnmatch.fnmatch>` or a compiled
`regex pattern instance <re.compile>` used to match the frame
by name.
Returns:
`Frame <tecplot.layout.Frame>`: The first `Frame <tecplot.layout.Frame>` identified by *pattern*.
.. note::
A `Page <tecplot.layout.Page>` can contain `Frames <tecplot.layout.Frame>` with identical names and only
the first match found is returned. This is not guaranteed to be
deterministic and care should be taken to have only `Frames
<tecplot.layout.Frame>` with unique names when this feature is used.
Example:
.. code-block:: python
:emphasize-lines: 11
import tecplot
page = tecplot.active_page()
frameA = page.add_frame()
frameA.name = 'A'
frameB = page.add_frame()
frameB.name = 'B'
assert frameB.active
assert frameA == page.frame('A')
"""
pattern_type = getattr(re, 'Pattern', getattr(re, '_pattern_type', None))
if not isinstance(pattern, pattern_type):
regex = re.compile(fnmatch.translate(pattern), re.IGNORECASE)
else:
regex = pattern
with self.activated():
frame = None
if _tecutil.FrameGetCount():
with session.suspend():
_tecutil.FrameLightweightLoopStart()
try:
while True:
if regex.match(_tecutil.FrameGetName()[1]):
frame = Frame(_tecutil.FrameGetUniqueID(), self)
break
if not _tecutil.FrameLightweightLoopNext():
break
finally:
_tecutil.FrameLightweightLoopEnd()
if __debug__:
if (
frame is None and
isinstance(pattern, str) and
any(c in pattern for c in '*?[]')
):
msg = 'no frame found matching: "{}"'.format(pattern)
warning = TecplotPatternMatchWarning(pattern, msg, 'glob')
warnings.warn(warning)
return frame
[docs]
@lock()
def frames(self, pattern=None):
"""Returns a `list` of `Frames <tecplot.layout.Frame>` matching the specified pattern.
Parameters:
pattern (`str` or `re.Pattern <re.compile>`, optional):
Case-insensitive `glob-style pattern string <fnmatch.fnmatch>`
or a compiled `regex pattern instance <re.compile>` used to
match frame names.
Returns:
`list`: `Frames <tecplot.layout.Frame>` identified by *pattern* or all frames if
no *pattern* is specified.
Example:
.. code-block:: python
import tecplot
page = tecplot.active_page()
page.add_frame() # create a second frame
# iterate over all frames and print their names
for frame in page.frames():
print(frame.name)
# store a persistent list of frames
frames = page.frames()
# prints: ['Frame 001', 'Frame 002']
print([f.name for f in frames])
"""
if pattern:
pattern_type = getattr(re, 'Pattern', getattr(re, '_pattern_type', None))
if not isinstance(pattern, pattern_type):
regex = re.compile(fnmatch.translate(pattern), re.IGNORECASE)
else:
regex = pattern
with self.activated():
framelist = []
if _tecutil.FrameGetCount():
with session.suspend():
_tecutil.FrameLightweightLoopStart()
try:
while True:
success, name = _tecutil.FrameGetName()
if success:
if pattern is None or regex.match(name):
framelist.append(Frame(_tecutil.FrameGetUniqueID(), self))
if not _tecutil.FrameLightweightLoopNext():
break
finally:
_tecutil.FrameLightweightLoopEnd()
if __debug__:
if (
not framelist and
pattern is not None and
isinstance(pattern, str) and
any(c in pattern for c in '*?[]')
):
msg = 'no frames found matching: "{}"'.format(pattern)
warning = TecplotPatternMatchWarning(pattern, msg, 'glob')
warnings.warn(warning)
return framelist
@property
@lock()
def exists(self):
"""Checks if the `Page <tecplot.layout.Page>` exists in the current layout.
This will return `False` after the `Page <tecplot.layout.Page>` has been deleted:
.. code-block:: python
import tecplot as tp
page = tp.add_page()
assert page.exists
tp.delete_page(page)
assert not page.exists
"""
current_page = _tecutil.PageGetUniqueID()
try:
for _ in range(_tecutil.PageGetCount()):
_tecutil.PageSetCurrentToNext()
if self.uid == _tecutil.PageGetUniqueID():
return True
return False
finally:
if current_page != _tecutil.PageGetUniqueID():
_tecutil.PageSetCurrentByUniqueID(current_page)
[docs]
@lock()
def tile_frames(self, mode=TileMode.Grid):
"""Tile frames based on a certain mode.
Parameters:
mode (`TileMode`, optional): Direction and layout mode for tiling
frames. Possible values: `TileMode.Grid` (default),
`TileMode.Columns`, `TileMode.Rows`, `TileMode.Wrap`.
Example usage::
>>> from tecplot.constant import TileMode
>>> page.tile_frame(TileMode.Wrap)
"""
with self.activated():
macro.execute_extended_command('Multi Frame Manager', mode.value)
[docs]
@lock_attributes
class Paper(object):
"""The `Paper` boundary defined on a workspace.
This is the area used for certain image output formats. It
is defined for a specific `Page <tecplot.layout.Page>`. `Frames <tecplot.layout.Frame>` can be
laid out in reference to this sub-area of the workspace.
"""
def __init__(self, page):
self.page = page
self._sv = page._sv + [sv.PAPER]
@property
def dimensions(self):
"""Width and height (read-only).
the dimensions, *(width, height)* in inches, of the
currently defined paper in the Tecplot workspace.
"""
with self.page.activated():
return _tecutil.PaperGetDimensions()