Source code for tecplot.annotation.annotation

from collections import namedtuple
from functools import wraps

from ..exception import *
from ..constant import *
from ..tecutil import _tecutil, lock, flatten_args, Index


def annotation_preamble(fn):
    """Things we want to do before calling the TecUtil annotation
       getter/setter methods."""
    @wraps(fn)
    def _fn(self, *a, **kw):
        with self.frame.activated():
            return fn(self, *a, **kw)
    return _fn


[docs]class Annotation(object): """An Annotation is a `Text` object which is attached to a `frame <Frame>`""" class _Iterator(object): def __init__(self, annotation_object_type, frame): self._annotation_object_type = annotation_object_type self._input_frame = frame assert annotation_object_type.__name__ in ('Text', 'Geom') tecutil_get_base = getattr( _tecutil, '{}GetBase'.format(annotation_object_type.__name__), None) self._tecutil_get_next = getattr( _tecutil, '{}GetNext'.format(annotation_object_type.__name__), None) # If there are no annotation objects attached to the frame, # tecutil_get_base() will return None and we will drop out # of the iteration loop the first time next() is called. self._current_annotation_id = tecutil_get_base() def next(self): # Currently Text_ID, Geom_ID types are ctypes.voidp's in pytecplot, # with TECUTIL_BAD_ID == None, so we check for None to indicate that # we are done iterating. # The fact that the ID's are voidp doesn't matter # because we're just passing the ID valid back to TecUtil without # trying to interpret it. if self._current_annotation_id is None: # e.g., we're done iterating. raise StopIteration() else: # Get the next annotation in the list annotation_id = self._current_annotation_id self._current_annotation_id = self._tecutil_get_next( self._current_annotation_id) # Return an annotation object (Text or Geom) # constructed from the id. Even the returned object will be # different from the original object in Python, # they will compare as equal since they have the same ID. # # Furthermore, changing the properties of one Text object # will change the properties of all objects with the same ID, # since all properties are stored in the engine and not # cached locally. return self._annotation_object_type(annotation_id, self._input_frame) def __next__(self): # python 3 return self.next() # python 2 def __iter__(self): return self _TecUtilPropertyAPI = namedtuple('API', 'getter setter') def __init__(self, uid, frame, annotation_object_type): if uid == TECUTIL_BAD_ID: raise TecplotLogicError('Error creating annotation object') self.uid = uid self.frame = frame assert annotation_object_type.__name__ in ('Text', 'Geom') # Declare the *_api properties here to help Intellisense find these # symbols (which are dynamically initialized later below). # Prepend the names with underscores so we don't pollute the # intellisense namespace. self._is_valid_api = getattr( _tecutil, '{}IsValid'.format(annotation_object_type.__name__)) self._anchor_position_api = None self._scope_api = None self._zone_or_map_api = None self._attached_api = None self._color_api = None self._clipping_api = None # # For TecUtil get/set methods common to Text and Geom annotations, # the TecUtil API to get/set those properties is symmetric, with # the only difference being 'Text' or 'Geom' in the function name. # # We can use this to dynamically create a table of TecUtil methods # which call either the Text or Geom version of the TecUtil function # depending on what type of annotation we are. # for name in [('anchor_position', 'AnchorPos'), ('scope', 'Scope'), ('zone_or_map', 'ZoneOrMap'), ('color', 'Color'), ('clipping', 'Clipping') ]: tecutil_getter = getattr( _tecutil, '{}Get{}'.format( annotation_object_type.__name__, name[1]), None) tecutil_setter = getattr( _tecutil, '{}Set{}'.format( annotation_object_type.__name__, name[1]), None) setattr(self, '_' + name[0] + '_api', ( Annotation._TecUtilPropertyAPI( getter=tecutil_getter, setter=tecutil_setter))) # The TecUtilXXXAttached getter and setter do not follow the pattern # of TecUtil[Text|Geom]Get/Set*, so create them by hand below attached_getter = getattr( _tecutil, "{}IsAttached".format(annotation_object_type.__name__)) attached_setter = getattr( _tecutil, "{}SetAttached".format(annotation_object_type.__name__)) self._attached_api = Annotation._TecUtilPropertyAPI( getter=attached_getter, setter=attached_setter ) _AnchorPosition = namedtuple('AnchorPosition', 'x y z') _AnchorPosition.__new__.__defaults__ = (0.0, 0.0, 0.0) @property @annotation_preamble def anchor_position(self): """ Get or set the anchor coordinate position (e.g., origin) of the `annotation object <Annotation>` in the current coordinate system. :type: 2-`tuple` of `floats <float>` x: X or Theta Position (default = 0.0) y: Y or R Position, (default = 0.0) Example showing how to set the anchor position of a `text <Text>` object:: >>> import tecplot as tp >>> text = tp.active_frame().add_3d_text("abc") >>> text.anchor_position = (1.0, 2.0) >>> text.anchor_position.x 1.0 >>> text.anchor_position.y 2.0 Raises: `TecplotLogicError` """ x, y, z = self._anchor_position_api.getter(self.uid) return Annotation._AnchorPosition(x, y, z) @anchor_position.setter @annotation_preamble def anchor_position(self, values): x, y, z = self._AnchorPosition(*flatten_args(*values)) # Can't use @lock decorator here because the unit tests need to be able # to query the property() object for each property, and the @lock # decorator combined with the @property decorator confuses getattr(). with lock(): self._anchor_position_api.setter( self.uid, float(x), float(y), float(z)) @property @annotation_preamble def scope(self): """ Get or Set the scope of the `annotation object <Annotation>`. :type: `Scope` `Annotations <Annotation>` with local scope are displayed only in the `frame <Frame>` in which they are created. If the `annotation <Annotation>` is defined as having `global <Scope.Global>` scope, it will appear in all "like" `frames <Frame>`. That is, those frames using the same data set as the one in which the `annotation <Annotation>` was created. (default = `Scope.Local`) Example showing how to set the scope of a `text <Text>` object: >>> import tecplot as tp >>> from tecplot.constant import * >>> text = tp.active_frame().add_text("abc") >>> text.scope = Scope.Global >>> text.scope Scope.Global Raises: `TecplotLogicError` """ return Scope(self._scope_api.getter(self.uid)) @scope.setter @annotation_preamble def scope(self, scope): with lock(): self._scope_api.setter(self.uid, scope.value) @property @annotation_preamble def zone_or_map(self): """ Get or set the zone or map `Index` to which the `annotation object <Annotation>` is associated (if it is attached). (Default: no default) :type: `Index` Example showing how to set the zone or map `Index` of a `text <Text>` object: >>> import tecplot as tp >>> text = tp.active_frame().add_text("abc") >>> text.zone_or_map = 1 >>> text.zone_or_map 1 Raises: `TecplotLogicError` """ return Index(self._zone_or_map_api.getter(self.uid)) @zone_or_map.setter @annotation_preamble def zone_or_map(self, zone_or_map): with lock(): self._attached_api.setter(self.uid, True) self._zone_or_map_api.setter(self.uid, Index(zone_or_map)) @property @annotation_preamble def attached(self): """Indicate if the `annotation object <Annotation>` should be attached to a `zone <Zone>` or map. (default = False) :type: `boolean <bool>` Example showing how to set the attached property of of a `text <Text>` object: >>> import tecplot as tp >>> text = tp.active_frame().add_text("abc") >>> text.zone_or_map = 1 >>> text.attached = True >>> text.zone_or_map 1 >>> text.attached True Raises: `TecplotLogicError` """ return self._attached_api.getter(self.uid) @attached.setter @annotation_preamble def attached(self, is_attached): with lock(): self._attached_api.setter(self.uid, is_attached) @property @annotation_preamble def color(self): """ Get or set the `color <Color>` of the `annotation object <Annotation>`. (default = `Color.Black`) :type: `Color` Example showing how to set the `color <Color>` of of a `text <Text>` object: >>> import tecplot as tp >>> from tecplot.constant import * >>> text = tp.active_frame().add_text("abc") >>> text.color = Color.Blue >>> text.Color Color.Blue Raises: `TecplotLogicError` """ return Color(self._color_api.getter(self.uid)) @color.setter @annotation_preamble def color(self, color): with lock(): self._color_api.setter(self.uid, color.value) @property @annotation_preamble def clipping(self): """Get or set the clipping properties of the `annotation object <Annotation>` :type: `Clipping` Clipping refers to displaying only that portion of an object that falls within a specified clipping region of the plot. (default = `Clipping.ClipToViewport`) If you have specified your text position in the Frame coordinate system, the `annotation object <Annotation>` will be clipped to the frame. If you have specified the Grid coordinate system, you can choose to clip your `annotation object <Annotation>` to the frame or the viewport. The size of the viewport depends on the plot type as follows: * 3D Cartesian - The viewport is the same as the frame, so viewport clipping is the same as frame clipping. * 2D Cartesian/XY Line - The viewport is defined by the extents of the X and Y axes. * Polar Line/Sketch - By default, the viewport is the same as the frame. Example showing how to set the clipping of a `text object <Text>`:: >>> import tecplot as tp >>> from tecplot.constant import * >>> text = tp.active_frame().add_text('abc') >>> text.clipping = Clipping.ClipToFrame >>> text.clipping Clipping.ClipToFrame Raises: `TecplotLogicError` """ return Clipping(self._clipping_api.getter(self.uid)) @clipping.setter @annotation_preamble def clipping(self, clipping): with lock(): self._clipping_api.setter(self.uid, clipping.value)