#!/usr/bin/env python
#
# $Id: vtkPipeline.py,v 1.23 2005/08/02 18:30:31 prabhu_r Exp $
#
# This python program/module creates a graphical VTK pipeline browser.  
# The objects in the pipeline can be configured.
#
# This code is distributed under the conditions of the BSD license.
# See LICENSE.txt for details.
#
# Copyright (c) 2000-2002, Prabhu Ramachandran.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE.  See the above copyright notice for more information.
#
# Author contact information:
#   Prabhu Ramachandran <prabhu_r@users.sf.net>
#   http://www.aero.iitm.ernet.in/~prabhu/

"""  This python program/module creates a graphical VTK pipeline
browser.   The pipeline tree is made using the TreeWidget from IDLE.
The objects in the pipeline can be configured.  The configuration is
done by using the ConfigVtkObj class.
"""

import string, re, types, Tkinter
import TreeWidget
import ConfigVtkObj

# set this to 1 if you want to see debugging messages - very useful if
# you have problems
DEBUG=0
def debug (msg):
    if DEBUG:
        print msg

# A hack to prevent vtkTransform.GetInverse() infinite loops
last_transform = 0

class vtkTreeNode (TreeWidget.TreeNode):

    """Overloaded TreeNode to do somethings I want.  Can do it better,
    but this works."""

    def __del__ (self):
        debug ("vtkTreeNode.__del__")

    def flip(self, event=None):
        if self.state == 'expanded':
            # changed by prabhu to stop unnecessary collapses
            #self.collapse()
            pass
        else:
            self.expand()
        self.item.OnDoubleClick()
        return "break"

    def get_all_children (self):
        "Recursively gets all the children of all nodes."
        self.get_children ()    
        for child in self.children:
            child.get_all_children ()

    def get_children (self):
        "This is used to get all the children to expand the tree."
        if not self.children:
            sublist = self.item._GetSubList ()
            if not sublist:
                return None
            for item in sublist:
                child = vtkTreeNode (self.canvas, self, item)
                self.children.append (child)    

    def expand_all (self):
        "This is used to expand all the children nodes."
        self.expand ()
        for child in self.children:
            child.expand_all ()


icon_map = {'RenderWindow': 'renwin', 'Renderer': 'ren',
            'Actor': 'actor', 'Light': 'light', 'Camera': 'camera',
            'Mapper': 'process', 'Property': 'file',
            'Coordinate': 'coord', 'Source': 'data', 
            'LookupTable': 'colormap', 'Reader': 'data',
            'Assembly': 'actor'}

def get_icon (vtk_obj):
    strng = vtk_obj.GetClassName ()[3:]
    for key in icon_map.keys ():
        if string.find (strng, key) > -1:
            return [key, vtk_obj, icon_map[key]]
    return ["", vtk_obj, "question"]


def remove_method (name, methods, method_names):
    "Removes methods if they have a particular name."
    
    debug ("vtkPipeline.py: remove_methods(\'%s\', methods, "\
           "method_names)"%name)
    try:
        idx = method_names.index(name)
    except ValueError:
        pass
    else:
        del methods[idx], method_names[idx]
    return methods, method_names
    

def get_methods (vtk_obj):
    """Obtain the various methods from the passed object.  This
    function also cleans up the method names to check for."""

    debug ("vtkPipeline.py: get_methods(vtk_obj)")
    methods = str (vtk_obj)
    methods = string.split (methods, "\n")
    del methods[0]

    # using only the first set of indented values.
    patn = re.compile ("  \S")
    for method in methods[:]:
        if patn.match (method):
            if string.find (method, ":") == -1:
                methods.remove (method)
            elif string.find (method[1], "none") > -1:
                methods.remove (method) 
        else:
            methods.remove (method)    

    method_names = []
    for i in range (0, len (methods)):
        strng = methods[i]
        strng = string.replace (strng, " ", "")
        methods[i] = string.split (strng, ":")
        method_names.append (methods[i][0])

    # Bug! :(
    # Severe bug - causes segfault with readers and renderwindows on
    # older VTK releases    
    if re.match ("vtk\w*RenderWindow", vtk_obj.GetClassName ()) or \
       re.match ("vtk\w*Reader", vtk_obj.GetClassName ()):
        remove_method('FileName', methods, method_names)

    if re.match ("vtk\w*Renderer", vtk_obj.GetClassName ()):
        methods.append (["ActiveCamera", ""])

    if re.match ("vtk\w*Assembly", vtk_obj.GetClassName ()):
        methods.append (["Parts", ""])
        methods.append (["Volumes", ""])
        methods.append (["Actors", ""])

    # fixes bug with infinite loop for GetInverse() method.  Thanks to
    # "Blezek, Daniel J" <blezek@crd.ge.com> for reporting it.    
    global last_transform
    if vtk_obj.IsA('vtkAbstractTransform'):
        if last_transform:
            remove_method('Inverse', methods, method_names)
        else:
            last_transform = last_transform + 1
    else:
        last_transform = 0

    # Some of these object are removed because they arent useful in
    # the browser.  I check for Source and Input anyway so I dont need
    # them.    
    for name in ('Output', 'FieldData', 'CellData', 'PointData',
                 'Source', 'Input', 'ExtentTranslator', 'Interactor',
                 'Information', 'Executive'):
        remove_method(name, methods, method_names)
    
    return methods


# A new idea - more general.  Using this I can obtain a lot more
# objects in the pipeline
def get_vtk_objs (vtk_obj):
    "Obtain vtk objects from the passed objects."

    debug ("vtkPipeline.py: get_vtk_objs (%s)"%
           (vtk_obj.GetClassName ()))
    methods = get_methods(vtk_obj)

    vtk_objs = []
    for method in methods:
        try:
            t = eval ("vtk_obj.Get%s ().GetClassName ()"%method[0])
        except:
            pass
        else:
            if string.find (t, "Collection") > -1:              
                col = eval ("vtk_obj.Get%s ()"%method[0])
                col.InitTraversal ()
                n = col.GetNumberOfItems ()
                prop = 0
                if re.match ("vtkProp\w*C", t):
                    prop = 1
                for i in range (0, n):
                    if prop:
                        obj = col.GetNextProp ()
                    else:
                        obj = col.GetNextItem ()
                    icon = get_icon (obj)
                    vtk_objs.append (["%s%d"%(icon[0], i), obj, icon[2]])
            else:
                obj = eval ("vtk_obj.Get%s ()"%method[0])
                vtk_objs.append (get_icon (obj))

    icon = icon_map['Source']
    try:
        obj = vtk_obj.GetSource ()
    except:
        pass
    else:
        if obj:
            vtk_objs.append (["Source", obj, icon])
    try: 
        n_s = vtk_obj.GetNumberOfSources ()
    except:
        pass
    else:
        for i in range (0, n_s):
            obj = vtk_obj.GetSource (i)
            vtk_objs.append (["Source%d"%i, obj, icon])

    if hasattr(vtk_obj, 'GetInput'):
        has_input = 1
        if hasattr(vtk_obj, 'GetNumberOfInputPorts'):
            if not vtk_obj.GetNumberOfInputPorts():
                has_input = 0
        if has_input:
            obj = vtk_obj.GetInput ()
            if obj and hasattr(obj, 'GetClassName'):
                icon = get_icon (obj)
                vtk_objs.append (icon)

    # Support for VTK-5.x.
    if hasattr(vtk_obj, 'GetProducerPort'):
        obj = vtk_obj.GetProducerPort().GetProducer()
        icon = get_icon(obj)
        vtk_objs.append(icon)
    
    return vtk_objs


class vtkTreeItem (TreeWidget.TreeItem):

    """A general VTK Tree item.  The parent TreeItem class is defined
    in TreeWidget.py"""

    def __init__ (self, name, obj, icon="question", renwin=None):
        "The icon variable corresponds to a file in the Icons dir."
        # renwin arg is to enable Render calls while doing config
        self.renwin = renwin
        self.icon = icon
        self.obj = obj  
        if obj:
            if name:
                self.name = "%s (%s)"%(name, obj.GetClassName ())
            else:
                self.name = "%s"%obj.GetClassName ()
            self.expandable = 1
        else:
            self.name = name
            self.expandable = 0

    def __del__ (self):
        debug ("vtkTreeItem.__del__")

    def GetText (self):
        return self.name

    def IsEditable (self):
        return 0

    def GetIconName (self):
        return self.icon

    def IsExpandable (self):
        return self.expandable

    def GetSubList (self):
        items = get_vtk_objs (self.obj)
        children = []
        for item in items:
            item.append (self.renwin)
            children.append (vtkTreeItem (*item))
        return children

    def OnDoubleClick (self):
        conf = ConfigVtkObj.ConfigVtkObj (self.renwin)
        conf.configure (None, self.obj)


class DummyTreeItem (TreeWidget.TreeItem):
    
    """ This is a dummy tree item that is used only for the
    vtkPipelineSegmentBrowser.  It is used so that you can visualize a
    segment of the pipeline starting from anywhere in the actual
    pipeline."""

    def __init__ (self, objs, renwin=None):
        # renwin arg is to enable Render calls while doing config
        self.renwin = renwin
        self.icon = "tk"
        self.objs = objs        

    def __del__ (self):
        debug ("DummyTreeItem.__del__")

    def GetText (self):
        return "..."

    def IsEditable (self):
        return 0

    def GetIconName (self):
        return self.icon

    def IsExpandable (self):
        return 1

    def GetSubList (self):
        children = []
        if (type (self.objs) is types.ListType) or \
           (type (self.objs) == types.TupleType):
            for obj in self.objs:
                l = get_icon (obj)
                l.append (self.renwin)
                children.append (vtkTreeItem (*l))
        else:
            l = get_icon (self.objs)
            l.append (self.renwin)
            children.append (vtkTreeItem (*l))
        return children

    def OnDoubleClick (self):
        pass


class vtkPipelineBrowser:
    "Browses the VTK pipleline given a vtkRenderWindow."

    def __init__ (self, master, renwin):
        "renwin is an instance of some vtkRenderWindow."
        self.renwin = renwin
        self.root = Tkinter.Toplevel (master)
        self.root.title ("VTK Pipeline Browser")
        self.root.focus_set ()
        self.root.protocol ("WM_DELETE_WINDOW", self.destroy)
        frame = Tkinter.Frame (self.root)
        frame.pack (side='top', expand=1, fill='both')
        self.sc = TreeWidget.ScrolledCanvas (frame, bg="white", 
                                             highlightthickness=0,
                                             takefocus=1)
        self.sc.frame.pack (expand=1, fill='both')
        f = Tkinter.Frame (self.root)
        f.pack (side='bottom')
        refr = Tkinter.Button (f, text="Refresh", underline=2, 
                               command=self.refresh)
        refr.grid (row=0, column=0, sticky='ew')
        ex_all = Tkinter.Button (f, text="Expand all", underline=0, 
                                 command=self.expand_all)
        ex_all.grid (row=0, column=1, sticky='ew')
        quit = Tkinter.Button (f, text="Close", underline=0,
                               command=self.quit)
        quit.grid (row=0, column=2, sticky='ew')
        
        self.root.bind ("<Alt-f>", self.refresh)
        self.root.bind ("<Alt-e>", self.expand_all)
        self.root.bind ("<Alt-c>", self.quit)

        self.root_node = None

    def browse (self):
        "Display the tree and interact with the user."
        self.clear ()
        self.root_item = vtkTreeItem ("RenderWindow", self.renwin, 
                                      "renwin", self.renwin)
        self.root_node = vtkTreeNode (self.sc.canvas, None, self.root_item)
        self.root_node.get_all_children ()
        self.root_node.expand ()

    def refresh (self, event=None):
        self.browse ()

    def expand_all (self, event=None):
        "Expand all nodes from the root down."
        self.root_node.expand_all ()

    def clear (self):
        if self.root_node:
            self.root_node.destroy ()        

    def destroy (self, event=None):
        self.quit (event)

    def quit (self, event=None):
        "Exit the browser."
        self.clear ()
        self.sc.canvas.destroy ()
        del self.sc
        del self.root_item
        del self.root_node
        self.root.destroy ()
        
    def isdestroyed ( self ):
        return not 'sc' in dir ( self );


class vtkPipelineSegmentBrowser (Tkinter.Frame):

    """Creates a frame containing a segment of the VTK pipleline given
    a set of VTK objects."""

    def __init__ (self, master, objs, renwin=None):
        "renwin is an instance of some vtkRenderWindow."
        Tkinter.Frame.__init__ (self, master)
        self.renwin = renwin
        self.frame = Tkinter.Frame (self)
        self.frame.pack (side='top', expand=1, fill='both')
        self.sc = TreeWidget.ScrolledCanvas (self.frame, bg="white", 
                                             highlightthickness=0,
                                             takefocus=1,
                                             width=200, height=150)
        self.sc.frame.pack (side='top', expand=1, fill='both')
        f = Tkinter.Frame (self.frame)
        f.pack (side='bottom' )
        refr = Tkinter.Button (f, text="Refresh", underline=2, 
                               command=self.browse)
        refr.grid (row=0, column=0, sticky='ew')
        ex_all = Tkinter.Button (f, text="Expand all", underline=0, 
                                 command=self.expand_all)
        ex_all.grid (row=0, column=1, sticky='ew')
        
        self.sc.canvas.bind ("<Alt-f>", self.browse)
        self.sc.canvas.bind ("<Alt-e>", self.expand_all)

        self.root_node = None
        self.root_item = None
        self.objs = objs
        self.browse ()

    def browse (self, event=None):
        "Display the tree and interact with the user."
        self.clear ()
        self.root_item = DummyTreeItem (self.objs, self.renwin)
        self.root_node = vtkTreeNode (self.sc.canvas, None, self.root_item)
        self.root_node.get_all_children ()
        self.root_node.expand ()

    def expand_all (self, event=None):
        "Expand all nodes from the root down."
        self.root_node.expand_all ()

    def clear (self):
        if self.root_node:
            self.root_node.destroy ()
        
    def destroy (self, event=None):
        "Close the browser."
        self.clear ()
        self.sc.canvas.destroy ()
        del self.sc
        del self.root_item
        del self.root_node
        self.frame.destroy ()

    def isdestroyed ( self ):
        return not 'sc' in dir (self)


def main ():
    import vtk
    from vtk.tk import vtkTkRenderWidget
    cone = vtk.vtkConeSource()
    cone.SetResolution(8)
    transform = vtk.vtkTransformFilter ()
    transform.SetInput ( cone.GetOutput() )
    transform.SetTransform ( vtk.vtkTransform() )
    coneMapper = vtk.vtkPolyDataMapper()
    coneMapper.SetInput(transform.GetOutput())
    l = vtk.vtkLookupTable ()
    coneMapper.SetLookupTable (l)
    coneActor = vtk.vtkActor()
    coneActor.SetMapper(coneMapper)    
    axes = vtk.vtkCubeAxesActor2D ()

    root = Tkinter.Tk ()
    wid = vtkTkRenderWidget.vtkTkRenderWidget (root, width=500, height=500)
    wid.pack (expand='true', fill='both')
    wid.bind ("<KeyPress-q>",
              lambda e=None: e.widget.winfo_toplevel().destroy())

    ren = vtk.vtkRenderer()
    renWin = wid.GetRenderWindow()
    renWin.AddRenderer(ren)
    renWin.SetSize(300,300)
    ren.AddActor (coneActor)
    ren.AddActor (axes)
    axes.SetCamera (ren.GetActiveCamera ())
    renWin.Render ()

    debug ("Starting VTK Pipeline Browser...")
    pipe = vtkPipelineBrowser (root, renWin)
    pipe.browse ()

    top = Tkinter.Toplevel (root)
    top.title ("VTK Pipeline Segment Browser")
    pipe_segment = vtkPipelineSegmentBrowser (top, (coneActor, axes),
                                              renWin)
    pipe_segment.pack (side='top', expand = 1, fill = 'both' )
    top.protocol ("WM_DELETE_WINDOW", top.destroy)
    root.mainloop ()

if __name__ == "__main__":
    main ()
