File: base.py

package info (click to toggle)
mayavi2 4.8.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 21,892 kB
  • sloc: python: 49,447; javascript: 32,885; makefile: 129; fortran: 60
file content (485 lines) | stat: -rw-r--r-- 16,452 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
"""The base object from which all MayaVi pipeline objects derive.

"""
# Author: Prabhu Ramachandran <prabhu_r@users.sf.net>
# Copyright (c) 2005-2020, Enthought, Inc.
# License: BSD Style.

# Standard library imports.
import pickle
from copy import deepcopy
import os
import logging
import importlib

# Enthought library imports.
from traits.api import (Any, Instance, Property, Bool, Str, Python,
                        HasTraits, WeakRef, on_trait_change)
from traitsui.api import TreeNodeObject
from tvtk.pyface.tvtk_scene import TVTKScene
from apptools.persistence import state_pickler
from pyface.resource.api import resource_path
from pyface.image_resource import ImageResource
from traitsui.menu import Menu, Action, Separator
from traitsui.api import View
from apptools.scripting.api import Recorder

# Local imports.
from mayavi.preferences.api import preference_manager
from mayavi.core.common import get_engine

# Setup a logger for this module.
logger = logging.getLogger(__name__)

# Subdirectory that the Base class will check for possible external views.
UI_DIR_NAME = ['ui']

# ------------------------------------------------------------------------------
#  The core tree node menu actions:
# ------------------------------------------------------------------------------

NewAction = 'NewAction'
CopyAction = Action(
    name='Copy',
    action='editor._menu_copy_node',
    enabled_when='editor._is_copyable(object)'
)
CutAction = Action(
    name='Cut',
    action='editor._menu_cut_node',
    enabled_when='editor._is_cutable(object)'
)
PasteAction = Action(
    name='Paste',
    action='editor._menu_paste_node',
    enabled_when='editor._is_pasteable(object)'
)
DeleteAction = Action(
    name='Delete',
    action='editor._menu_delete_node',
    enabled_when='editor._is_deletable(object)'
)
RenameAction = Action(
    name='Rename',
    action='editor._menu_rename_node',
    enabled_when='editor._is_renameable(object)'
)
standard_menu_actions = [
    Separator(),
    CutAction,
    CopyAction,
    PasteAction,
    Separator(),
    RenameAction,
    DeleteAction,
    Separator(),
]


######################################################################
# `Base` class.
######################################################################
class Base(TreeNodeObject):
    # The version of this class.  Used for persistence.
    __version__ = 0

    ########################################
    # Traits

    # The scene (RenderWindow) associated with this component.
    scene = Instance(TVTKScene, record=False)

    # Is this object running as part of the mayavi pipeline.
    running = Property(Bool, record=False)

    # The object's name.
    name = Str('')

    # The default icon.
    icon = 'module.ico'

    # The human readable type for this object
    type = Str('', record=False)

    # Is this object visible or not.
    visible = Bool(True, desc='if the object is visible')

    # Extend the children list with an AdderNode when a TreeEditor needs it.
    children_ui_list = Property(depends_on=['children'], record=False)

    # The parent of this object, i.e. self is an element of the parents
    # children.  If there is no notion of a parent/child relationship
    # this trait is None.
    parent = WeakRef(record=False)

    # A helper for the right click menus, context sensitivity etc.
    menu_helper = Instance(HasTraits, record=False)

    # Our recorder.
    recorder = Instance(Recorder, record=False)

    ##################################################
    # Private traits
    _is_running = Bool(False)

    # This is used to save the state of the object when it is not
    # running.  When the object "starts", the state is loaded.  This
    # is done because a stopped object will not have a meaningful VTK
    # pipeline setup, so setting its state will lead to all kinds of
    # errors.
    _saved_state = Any('')

    # Hide and show actions
    _HideShowAction = Instance(Action,
                               kw={'name': 'Hide/Show',
                                   'action': 'object._hideshow'}, )

    # The menu shown on right-click for this.
    _menu = Instance(Menu, transient=True)

    # Path to the icon for this object.
    _icon_path = Str()

    # Adder node: a dialog to add children to this object
    _adder_node_class = None

    # Name of the file that may host the hand-crafted view
    _view_filename = Str(transient=True)

    # Hand crafted view.
    _module_view = Instance(View, transient=True)

    # Work around problem with HasPrivateTraits.
    __ = Python
    ##################################################

    ######################################################################
    # `object` interface
    ######################################################################
    def __get_pure_state__(self):
        """Method used by the state_pickler.
        """
        d = self.__dict__.copy()
        for attr in ('scene', '_is_running', '__sync_trait__',
                     '__traits_listener__', '_icon_path',
                     '_menu', '_HideShowAction', 'menu_helper',
                     'parent', 'parent_', '_module_view', '_listener_cache',
                     '_view_filename', 'mlab_source'):
            d.pop(attr, None)
        return d

    def __getstate__(self):
        """Allows standard pickle to work via the state_pickler.
        """
        return state_pickler.dumps(self)

    def __setstate__(self, str_state):
        """Allows standard pickle to work via the state_pickler.
        """
        self.__init__()
        # Get the state from the string and update it.
        state = state_pickler.loads_state(str_state)
        state_pickler.update_state(state)
        # Save the state and load it if we are running.
        self._saved_state = pickle.dumps(state)
        if self.running:
            self._load_saved_state()

    def __deepcopy__(self, memo):
        """Method used by copy.deepcopy().  This also uses the
        state_pickler to work correctly.
        """
        # Create a new instance.
        new = self.__class__()
        # If we have a saved state, use it for the new instance.  If
        # not, get our state and save that.
        saved_state = self._saved_state
        if len(saved_state) == 0:
            state = state_pickler.get_state(self)
            # FIXME: This is for streamline seed point widget position which
            # does not get serialized correctly
            try:
                st = state.children[0].children[4]
                l_pos = st.seed.widget.position
                st.seed.widget.position = [pos.item() for pos in l_pos]
            except (IndexError, AttributeError):
                pass
            saved_state = pickle.dumps(state)
        new._saved_state = saved_state
        # In the unlikely case that a new instance is running, load
        # the saved state.
        if new.running:
            new._load_saved_state()
        return new

    ######################################################################
    # `Base` interface
    ######################################################################
    def start(self):
        """Invoked when this object is added to the mayavi pipeline.
        """
        self.running = True
        self._load_saved_state()

    def stop(self):
        """Invoked when this object is removed from the mayavi
        pipeline.
        """
        self.running = False

    def add_child(self, child):
        """This method intelligently adds a child to this object in
        the MayaVi pipeline.
        """
        raise NotImplementedError

    def remove_child(self, child):
        """Remove specified child from our children.
        """
        raise NotImplementedError()

    def remove(self):
        """Remove ourselves from the mayavi pipeline.
        """
        if self.parent is not None:
            e = get_engine(self)
            self.parent.remove_child(self)
            if e.current_object is self:
                e.current_object = self.parent

    def render(self):
        """Invokes render on the scene, this in turn invokes Render on
        the VTK pipeline.
        """
        s = self.scene
        if s is not None:
            s.render()

    def dialog_view(self):
        """ Returns a view with an icon and a title.
        """
        view = self.trait_view()
        icon = self._icon_path + os.sep + 'images' + os.sep + self.icon
        view.icon = ImageResource(icon)
        view.title = "Edit%s: %s" % (self.type, self.name)
        view.buttons = ['OK', 'Cancel']
        return view

    def trait_view(self, name=None, view_element=None):
        """ Gets or sets a ViewElement associated with an object's class.

        Overridden here to search for a separate file in the same directory
        for the view to use for this object. The view should be declared in
        the file named <class name>_view. If a file with this name is not
        found, the trait_view method on the base class will be called.
        """

        # If a name is specified, then call the HasTraits trait_view method
        # which will return (or assign) the *view_element* associated with
        # *name*.
        if name:
            return super(Base, self).trait_view(name, view_element)

        view = self._load_view_cached(name, view_element)
        # Uncomment this when developping views.
        # view = self._load_view_non_cached(name, view_element)
        return view

    ######################################################################
    # `TreeNodeObject` interface
    ######################################################################
    def tno_get_label(self, node):
        """Gets the label to display for a specified object.
        """
        if self.name == '':
            self.name = self.__class__.__name__
        return self.name

    def tno_get_view(self, node):
        """Gets the View to use when editing an object.
        """
        view = self.trait_view()
        view.kind = 'subpanel'
        return view

    def tno_confirm_delete(self, node):
        """Confirms that a specified object can be deleted or not.
        """
        if preference_manager.root.confirm_delete:
            return None
        else:
            return True

    def tno_get_menu(self, node):
        """ Returns the contextual pop-up menu.
        """
        if self._menu is None:
            return super(Base, self).tno_get_menu(node)
        return self._menu

    def tno_get_icon(self, node, is_expanded):
        return self.icon

    def tno_get_icon_path(self, node):
        return self._icon_path

    def tno_delete_child(self, node, index):
        if len(self.children_ui_list) > len(self.children):
            del self.children[index - 1]
        else:
            del self.children[index]

    def tno_append_child(self, node, child):
        """ Appends a child to the object's children.
        """
        self.children.append(child)

    def tno_insert_child(self, node, index, child):
        """ Inserts a child into the object's children.
        """
        if len(self.children_ui_list) > len(self.children):
            idx = index - 1
        else:
            idx = index
        self.children[idx:idx] = [child]

    ######################################################################
    # Non-public interface
    ######################################################################
    def _get_running(self):
        return self._is_running

    def _set_running(self, new):
        if self._is_running == new:
            return
        else:
            old = self._is_running
            self._is_running = new
            self.trait_property_changed('running', old, new)

    def _get_children_ui_list(self):
        """ Getter for Traits Property children_ui_list.

        For the base class, do not add anything to the children list.
        """
        if ((not preference_manager.root.show_helper_nodes or
             len(self.children) > 0)
            or self._adder_node_class is None
            or (not self.type == ' scene' and
                'none' in self.output_info.datasets)):
            # We can't use isinstance, as we would have circular
            # imports
            return self.children
        else:
            return [self._adder_node_class(object=self)]

    @on_trait_change('children[]')
    def _trigger_children_ui_list(self, old, new):
        """ Trigger a children_ui_list change when scenes changed.
        """
        self.trait_property_changed('children_ui_list', old, new)

    def _visible_changed(self, value):
        # A hack to set the name when the tree view is not active.
        # `self.name` is set only when tno_get_label is called and this
        # is never called when the tree view is not shown leading to an
        # empty name.
        if len(self.name) == 0:
            self.tno_get_label(None)
        if value:
            self.name = self.name.replace(' [Hidden]', '')
        else:
            n = self.name
            if ' [Hidden]' not in n:
                self.name = "%s [Hidden]" % n

    def _load_view_cached(self, name, view_element):
        """ Use a cached view for the object, for faster refresh.
        """
        if self._module_view is not None:
            view = self._module_view
        else:
            logger.debug("No view found for [%s] in [%s]. "
                         "Using the base class trait_view instead.",
                         self, self._view_filename)
            view = super(Base, self).trait_view(name, view_element)
        return view

    def _load_view_non_cached(self, name, view_element):
        """ Loads the view by execing a file. Useful when tweaking
            views.
        """
        result = {}
        view_filename = self._view_filename
        try:
            exec(compile(
                open(view_filename).read(), view_filename, 'exec'), {}, result
            )
            view = result['view']
        except IOError:
            logger.debug("No view found for [%s] in [%s]. "
                         "Using the base class trait_view instead.",
                         self, view_filename)
            view = super(Base, self).trait_view(name, view_element)
        return view

    def _hideshow(self):
        if self.visible:
            self.visible = False
        else:
            self.visible = True

    def _load_saved_state(self):
        """Load the saved state (if any) of this object.
        """
        saved_state = self._saved_state
        if len(saved_state) > 0:
            state = pickle.loads(saved_state)
            if hasattr(self, '__set_pure_state__'):
                self.__set_pure_state__(state)
            else:
                state_pickler.set_state(self, state)
            self._saved_state = ''

    def __view_filename_default(self):
        """ The name of the file that will host the view.
        """
        module = self.__module__.split('.')
        class_filename = module[-1] + '.py'
        module_dir_name = module[1:-1]
        base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        view_filename = os.path.join(*([base_dir] + module_dir_name
                                       + UI_DIR_NAME + [class_filename]))
        return view_filename

    def __module_view_default(self):
        """ Try to load a view for this object.
        """
        view_filename = self._view_filename
        if os.path.exists(view_filename):
            spec = importlib.util.spec_from_file_location(
                'view', view_filename
            )
            module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(module)
            view = module.view
        else:
            view = None
        return view

    def __menu_default(self):
        extras = []
        if self.menu_helper is not None:
            extras = self.menu_helper.actions + self._extra_menu_items()
        menu_actions = [Separator()] + extras + \
            [Separator(), self._HideShowAction, Separator()] + \
            deepcopy(standard_menu_actions)
        return Menu(*menu_actions)

    def __icon_path_default(self):
        return resource_path()

    def _extra_menu_items(self):
        """Override this to generate any new menu actions you want on
        the right click menu."""
        return []