"""

This module shows an iso-surface of scalar data. This will work for
any dataset.

This code is distributed under the conditions of the BSD license.  See
LICENSE.txt for details.

Copyright (c) 2001-2002, Prabhu Ramachandran.
"""

__author__ = "Prabhu Ramachandran <prabhu_r@users.sf.net>"
__version__ = "$Revision: 1.5 $"
__date__ = "$Date: 2005/08/02 18:30:13 $"

import Base.Objects, Common
import Tkinter, tkColorChooser, math
import vtk
import vtkPipeline.vtkMethodParser

debug = Common.debug

class IsoSurface (Base.Objects.Module):

    """ This module shows an iso-surface of scalar data. This will
    work for any dataset."""

    def __init__ (self, mod_m): 
        debug ("In IsoSurface::__init__ ()")
        Common.state.busy ()
        Base.Objects.Module.__init__ (self, mod_m)
        self.cont_fil = vtk.vtkContourFilter ()
        self.norm = vtk.vtkPolyDataNormals ()
        self.mapper = self.map = vtk.vtkPolyDataMapper ()
        self.actor = self.act = vtk.vtkActor ()
        self.data_out = self.mod_m.GetOutput ()
        self._initialize ()
        self._gui_init ()
        if not self.mod_m.get_scalar_data_name ():
            msg = "Warning: No scalar data present to contour!"
            Common.print_err (msg)
        self.renwin.Render ()
        Common.state.idle ()

    def __del__ (self): 
        debug ("In IsoSurface::__del__ ()")
        if self.act:
            self.renwin.remove_actors (self.act)
        self.renwin.Render ()

    def _initialize (self):
        debug ("In IsoSurface::_initialize ()")
        self.cont_fil.SetInput (self.data_out)
        self.map.SetInput (self.cont_fil.GetOutput ())
        self.map.SetLookupTable (self.mod_m.get_scalar_lut ())
        dr = self.mod_m.get_scalar_data_range ()
        self.data_range = dr
        self.map.SetScalarRange (dr)
        self.act.SetMapper (self.map)
        #self.act.GetProperty ().BackfaceCullingOff ()
        #self.act.GetProperty ().FrontfaceCullingOff ()
        midrange = (dr[0] + dr[1])*0.5
        self.slider_pos = midrange
        self.cont_fil.SetValue (0, midrange)
        self.renwin.add_actors (self.act)
        # used for the pipeline browser
        self.pipe_objs = self.act
        
    def _gui_init (self): 
        debug ("In IsoSurface::_gui_init ()")
        self.slider = None
        self.contour_var = Tkinter.DoubleVar ()
        self.contour_var.set (self.slider_pos)
        self.c_entry_var = Tkinter.DoubleVar ()
        self.c_entry_var.set (self.slider_pos)
        dr = self.mod_m.get_scalar_data_range ()
        self.min_cnt = Tkinter.DoubleVar ()
        self.max_cnt = Tkinter.DoubleVar ()
        self.min_cnt.set (dr[0])
        self.max_cnt.set (dr[1])
        self.normals_on = Tkinter.IntVar ()
        self.normals_on.set (0)
        self.angle_var = Tkinter.DoubleVar ()
        self.angle_var.set (45)
        self._auto_sweep_init ()
        self.sweep_step.set (15)
        
    def SetInput (self, source): 
        debug ("In IsoSurface::SetInput ()")
        Common.state.busy ()
        self.data_out = source
        self.cont_fil.SetInput (self.data_out)
        dr = self.mod_m.get_scalar_data_range ()
        if (dr[0] != self.data_range[0]) or (dr[1] != self.data_range[1]):
            self.data_range = dr
            self.map.SetScalarRange (dr)
            self.min_cnt.set (dr[0])
            self.max_cnt.set (dr[1])        
            self.c_entry_var.set ((dr[0] + dr[1])*0.5)
            self.change_contour_limits ()
            self.change_contour_entry ()
        Common.state.idle ()

    def save_config (self, file): 
        debug ("In IsoSurface::save_config ()")
        file.write ("%f, %d, %f, %f, %f\n"%(self.c_entry_var.get (),
                                            self.normals_on.get (),
                                            self.angle_var.get (),
                                            self.min_cnt.get (),
                                            self.max_cnt.get ()))
        p = vtkPipeline.vtkMethodParser.VtkPickler ()
        for obj in (self.cont_fil, self.norm, self.map,
                    self.act, self.act.GetProperty ()):
            p.dump (obj, file)

    def load_config (self, file): 
        debug ("In IsoSurface::load_config ()")
        c_val, norm_on, ang, min_cnt, max_cnt = eval (file.readline ())
        self.slider_pos  = c_val
        self.contour_var.set (c_val)
        self.c_entry_var.set (c_val)
        self.normals_on.set (norm_on)
        self.angle_var.set (ang)
        self.min_cnt.set (min_cnt)
        self.max_cnt.set (max_cnt)
        p = vtkPipeline.vtkMethodParser.VtkPickler ()
        for obj in (self.cont_fil, self.norm, self.map,
                    self.act, self.act.GetProperty ()):        
            p.load (obj, file)

        self.change_contour_slider ()
        self.do_normals ()
        
    def config_changed (self): 
        debug ("In IsoSurface::config_changed ()")
        pass

    def make_main_gui (self): 
        debug ("In IsoSurface::make_main_gui ()")
        frame = Tkinter.Frame (self.root, relief='ridge', bd=2)
        frame.pack (side='top', fill='both', expand=1)

        name = "Scalar Variable: " + self.mod_m.get_scalar_data_name ()
        dr = (self.min_cnt.get (), self.max_cnt.get ())
        resolution = (dr[1] - dr[0])/1000
        self.slider = Tkinter.Scale (frame, label=name,
                                     resolution=resolution,
                                     variable=self.contour_var,
                                     from_=dr[0], to=dr[1], length="8c",
                                     orient='horizontal')

        self.slider.grid (row=0, column=0, columnspan=2, sticky='ew')
        self.slider.bind ("<ButtonRelease>", self.change_contour_slider )

        lab = Tkinter.Label (frame, text="Iso-surface value :")
        lab.grid (row=1, column=0, sticky='w')
        entry = Tkinter.Entry (frame, width=10, relief='sunken',
                               textvariable=self.c_entry_var)
        entry.grid (row=1, column=1, sticky='ew')
        entry.bind ("<Return>", self.change_contour_entry)

        lab = Tkinter.Label (frame, text="Minimum contour:")
        lab.grid (row=2, column=0, sticky='w')
        entry = Tkinter.Entry (frame, width=10, relief='sunken', 
                               textvariable=self.min_cnt)
        entry.grid (row=2, column=1, sticky='we')
        entry.bind ("<Return>", self.change_contour_limits)

        lab = Tkinter.Label (frame, text="Maximum contour:")
        lab.grid (row=3, column=0, sticky='w')
        entry = Tkinter.Entry (frame, width=10, relief='sunken', 
                               textvariable=self.max_cnt)
        entry.grid (row=3, column=1, sticky='we')
        entry.bind ("<Return>", self.change_contour_limits)

        norm = Tkinter.Checkbutton (frame, text="Compute PolyDataNormals", 
                                    variable=self.normals_on, onvalue=1,
                                    offvalue=0, command=self.do_normals)
        norm.grid (row=4, columnspan=2, sticky='w')
        lab = Tkinter.Label (frame, text="Feature Angle (in degrees): ")
        lab.grid (row=5, column=0, sticky='w')
        entry = Tkinter.Entry (frame, width=10, relief='sunken', 
                               textvariable=self.angle_var)
        entry.grid (row=5, column=1, sticky='ew')
        entry.bind ("<Return>", self.do_normals)

        self.make_actor_gui ()
        self.make_auto_sweep_gui ()
    
    def do_normals (self, event=None):
        debug ("In IsoSurface::do_normals ()")
        Common.state.busy ()
        if self.normals_on.get ():
            self.norm.SetInput (self.cont_fil.GetOutput ())
            self.norm.SetFeatureAngle (self.angle_var.get ())
            self.map.SetInput (self.norm.GetOutput ())
        else:
            self.map.SetInput (self.cont_fil.GetOutput ())
        self.renwin.Render ()
        Common.state.idle ()

    def change_contour_limits (self, event=None):
        debug ("In IsoSurface::change_contour_limits ()")
        min_cnt = self.min_cnt.get ()
        max_cnt = self.max_cnt.get ()
        val = self.contour_var.get ()
        dr = self.data_range

        if max_cnt < val:
            msg = "Error: max. contour value less than current "\
                  "contour value."
            debug (msg)
            max_cnt = dr[1]
            self.max_cnt.set (max_cnt)

        if min_cnt > val:
            msg = "Error: min. contour value larger than current "\
                  "contour value."
            debug (msg)
            min_cnt = dr[0]
            self.min_cnt.set (min_cnt)

        resolution = (max_cnt - min_cnt)/1000
        if self.slider:
            self.slider.config (from_=min_cnt, to=max_cnt,
                                resolution=resolution)

    def change_contour_slider (self, event=None):
        debug ("In IsoSurface::change_contour_slider ()")
        Common.state.busy ()
        self.slider_pos = self.contour_var.get ()
        self.c_entry_var.set (self.slider_pos)
        self.cont_fil.SetValue (0, self.slider_pos)
        self.renwin.Render ()
        Common.state.idle ()

    def change_contour_entry (self, event=None):
        debug ("In IsoSurface::change_contour_entry ()")
        Common.state.busy ()
        self.slider_pos = self.c_entry_var.get ()
        self.contour_var.set (self.slider_pos)
        self.cont_fil.SetValue (0, self.slider_pos)
        self.renwin.Render ()
        Common.state.idle ()

    def do_sweep (self, event=None):
        debug ("In IsoSurface::do_sweep ()")
        if self.sweep_var.get ():
            val = int (1000*self.sweep_delay.get ())
            self.root.after (val, self.update_sweep)

    def update_sweep (self):
        debug ("In IsoSurface::update_sweep ()")
        if self.sweep_var.get ():
            min_cnt = self.min_cnt.get ()
            max_cnt = self.max_cnt.get ()
            d_pos = (max_cnt - min_cnt)/self.sweep_step.get ()
            pos = self.slider_pos + d_pos
            if (d_pos > 0) and (pos >= max_cnt):
                pos = min_cnt
            elif (d_pos < 0) and (pos <= min_cnt):
                pos = max_cnt
            self.contour_var.set (pos)
            self.change_contour_slider ()            
            val = int (1000*self.sweep_delay.get ())
            self.root.after (val, self.update_sweep)

    def close_gui (self, event=None):
        debug ("In IsoSurface::close_gui ()")
        Base.Objects.Module.close_gui (self, event)
        self.slider = None
