Package windSimSuite :: Package interface :: Module manager
[hide private]

Source Code for Module windSimSuite.interface.manager

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  # 
  4  # This file is part of MBDyn sim suite. 
  5  # Copyright (C) 2007 André ESPAZE, as part of a Master thesis supervised by 
  6  # Martin O.L.Hansen (DTU) and Nicolas Chauvat (Logilab) 
  7   
  8  # MBDyn Sim Suite is free software; you can redistribute it and/or modify 
  9  # it under the terms of the GNU General Public License as published by 
 10  # the Free Software Foundation; either version 2 of the License, or 
 11  # (at your option) any later version. 
 12  # 
 13  # This program is distributed in the hope that it will be useful, 
 14  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 16  # GNU General Public License for more details. 
 17  # 
 18  # You should have received a copy of the GNU General Public License 
 19  # along with this program; if not, write to the Free Software 
 20  # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
 21  # 
 22  """The glue between the main application and the simulation. This job 
 23  is achieved by the L{Manager} that coordinates GTK events with a VTK area. 
 24  """ 
 25  import gtk 
 26  import gobject 
 27     
 28  from mbdyn.interface.nodes import NodeMenu 
 29  from mbdyn.interface.references import ReferenceFrameMenu 
 30  from mbdyn.interface.groups import ReferenceListMenu 
 31  from mbdyn.interface.vectors import ReferenceFrame as vtkReferenceFrame 
 32   
 33  from windSimSuite.interface.tower import TowerMenu 
 34  from windSimSuite.interface.nacelle import NacelleMenu 
 35  from windSimSuite.interface.rotor import HubMenu, RotorMenu 
 36  from windSimSuite.interface.blade import BladeMenu 
 37  from windSimSuite.interface.sections import SectionMenu 
 38   
 39  from windSimSuite.interface.vtk_area import GtkVtkArea 
 40  from windSimSuite.interface.matplotlib_manager import FigureManager 
 41  from windSimSuite.interface.main import PyMBDynFile 
 42   
 43  from mbdyn.interface.vectors import Arrow as ArrowMapper 
 44   
 45  MENU_TABLE = {} 
 46  MENU_TABLE["node"] = NodeMenu() 
 47  MENU_TABLE["reference_frame"] = ReferenceFrameMenu() 
 48   
 49  MENU_TABLE["reference_list"] = ReferenceListMenu() 
 50   
 51  MENU_TABLE["tower"] = TowerMenu() 
 52  MENU_TABLE["nacelle"] = NacelleMenu() 
 53  MENU_TABLE["rotor"] = RotorMenu() 
 54  MENU_TABLE["hub"] = HubMenu() 
 55  MENU_TABLE["blade"] = BladeMenu() 
 56  MENU_TABLE["section"] = SectionMenu() 
 57   
 58   
59 -class ScaleTable:
60 """A table for scaling the simulation in 3D. 61 This table is shared between the simulation 62 and the manager""" 63
64 - def __init__(self, widgets, win_interactor):
65 self.widget = widgets["scale_table"] 66 self.spin = {} 67 for key in ["scale_factor", "unit_vector"]: 68 self.spin[key] = widgets["spinbutton_" + key] 69 self.spin[key].connect("activate", self.redraw) 70 71 self.spin["scale_factor"].set_value(0.2) 72 self.spin["unit_vector"].set_value(5.) 73 74 self.win_interactor = win_interactor 75 76 self.objs = [] 77 self.vectors = []
78
79 - def redraw(self, spinbutton=None):
80 """Redraw all the objects. They are contained in C{objs} attribute, 81 a new scale factor is set and a new definition of the unit length 82 is given""" 83 for obj in self.objs + self.vectors: 84 obj.set_scale_factor(self.spin["scale_factor"].get_value()) 85 for vector in self.vectors: 86 vector.set_norm(self.spin["unit_vector"].get_value()) 87 self.win_interactor.Render()
88
89 - def add(self, obj):
90 """Add the reference of an object to scale, 91 from the simulation""" 92 self.objs.append(obj)
93
94 - def add_vector(self, obj):
95 """Add a vector to scale""" 96 self.vectors.append(obj)
97
98 - def reset(self):
99 """Reset for a new simulation""" 100 self.objs = [] 101 self.vectors = []
102
103 - def show(self):
104 """Show the scale table""" 105 self.widget.show()
106
107 - def hide(self):
108 """Hide the scale table""" 109 self.widget.hide()
110 111
112 -class StatusBar:
113 """A custom GTK status bar. It does not need any context id 114 to write something, the message_id is then kept 115 for cleaning. This C{StatusBar} is shared between 116 the widgets to express their operations to the interface. 117 """ 118
119 - def __init__(self, widget):
120 self.widget = widget 121 desc = "Wind sim suite actions" 122 self.context_id = self.widget.get_context_id(desc) 123 self.message_id = self.widget.push(self.context_id, "")
124
125 - def write(self, message):
126 """Write a message on the status bar""" 127 self._clean() 128 self.message_id = self.widget.push(self.context_id, message)
129
130 - def _clean(self):
131 """Clean the status bar""" 132 self.widget.remove(self.context_id, self.message_id)
133 134
135 -class TreeView(gtk.TreeView):
136 """A GTK treeview filled of simulation objects 137 """ 138
139 - def __init__(self, status_bar, title="Wind turbine"):
140 self.model = gtk.TreeStore(gobject.TYPE_PYOBJECT, 141 gobject.TYPE_STRING) 142 gtk.TreeView.__init__(self, self.model) 143 144 self.status_bar = status_bar 145 146 self.set_rules_hint(True) 147 148 self.column_id = {"obj" : 0, "text" : 1} 149 renderer = gtk.CellRendererText() 150 column = gtk.TreeViewColumn(title, 151 renderer, 152 text=self.column_id["text"]) 153 self.append_column(column)
154
155 - def connect_button_press_event(self, callback):
156 """Connect the callback when the mouse button is pressed""" 157 self.connect("button-press-event", callback)
158
159 - def get_object(self, event):
160 """Return the object reference in case of a right click""" 161 if event.button == 3: 162 xpos, ypos = map(int, [event.x, event.y]) 163 try: 164 path, col, cellx, celly = self.get_path_at_pos(xpos, ypos) 165 except TypeError: 166 return True 167 selection = self.get_selection() 168 if not selection.path_is_selected(path): 169 self.set_cursor(path, col, 0) 170 171 model, giter = selection.get_selected() 172 if giter: 173 return model.get_value(giter, self.column_id["obj"]) 174 else: 175 return None
176
177 - def clean(self):
178 """Clear the model for welcoming a new simulation""" 179 self.model.clear()
180
181 - def get_top(self):
182 """Return the C{TreeIter} instance at the top""" 183 return self.model.append(None)
184
185 - def get_from(self, giter):
186 """Return the C{TreeIter} instance from the one given""" 187 return self.model.append(giter)
188
189 - def add_at(self, giter, obj):
190 """Add an object at a particular C{TreeIter} instance""" 191 self.model.set(giter, self.column_id["obj"], obj) 192 self.model.set(giter, self.column_id["text"], obj.name)
193 194
195 -class AnimationToolbar:
196 """The widget managing the simulation animation. 197 198 This object is responsible to always give the C{current_frame_id} 199 of the animation, it keeps as a result a simulation reference 200 given by the manager. 201 To express itselfs, the C{AnimationToolbar} needs also the VTK 202 window interactor, to refresh the view, and the status bar, 203 to tell what is happening. 204 """ 205
206 - def __init__(self, win_interactor, status_bar, widgets):
207 self.win_interactor = win_interactor 208 self.status_bar = status_bar 209 210 self.spin_button = widgets["frame_step_spinbutton"] 211 self.spin_button.add_events(gtk.gdk.LEAVE_NOTIFY) 212 self.spin_button.connect("leave_notify_event", 213 self.remove_spin_button_focus) 214 215 self.time_label = widgets["time_label"] 216 self.frame_scale = widgets["hscale"] 217 self.frame_adjustement = self.frame_scale.get_adjustment() 218 self.frame_adjustement.connect("value_changed", 219 self._adjust_simulation) 220 221 self.button = {} 222 for key in ["first_frame", "play", "last_frame", "stop"]: 223 self.button[key] = widgets[key] 224 widgets[key].connect("toggled", self.button_callback, key) 225 226 self.button_action = { 227 "first_frame" : self.set_first_frame, 228 "play" : self.play_animation, 229 "last_frame" : self.set_last_frame, 230 "stop" : self.stop_animation 231 } 232 233 self.simu = None 234 235 self.current_frame_id = 0 236 self.animation_id = None
237
238 - def remove_spin_button_focus(self, *args):
239 """Remove the focus from the spin button""" 240 self._adjustement_setup() 241 self.frame_scale.grab_focus()
242
243 - def set_simulation(self, simu):
244 """Set a C{Simulation} instance""" 245 self.simu = simu 246 if simu.has_time: 247 self.button["first_frame"].set_active(True) 248 self._adjustement_setup() 249 else: 250 self._adjustement_reset()
251
252 - def _adjustement_reset(self):
253 """Freeze the adjustement, when the simulation can not be 254 explored along time""" 255 self.frame_scale.set_range(0, 0) 256 self.frame_scale.set_increments(0., 0.)
257
258 - def _adjustement_setup(self):
259 """Setup the adjustement when a new simulation is loaded or 260 when the frame step is changed.""" 261 frame_step = self.spin_button.get_value_as_int() 262 self.frame_scale.set_range(0, len(self.simu.results.time) - 1) 263 self.frame_scale.set_increments(frame_step, 264 self._get_page_increment(frame_step))
265 #self.frame_adjustement.emit("changed") 266
267 - def _get_page_increment(self, frame_step):
268 """Supposed to set the page increment for faster navigation, 269 TODO""" 270 return 2 * frame_step
271
272 - def _adjust_simulation(self, adjustement):
273 """Get back the current frame id and update the simulation""" 274 self.current_frame_id = int(adjustement.value) 275 self.set_simulation_at_current_frame()
276
277 - def button_callback(self, widget, button_key):
278 """A callback when one of the button toolbar is pressed""" 279 if widget.get_active(): 280 self.button_action[button_key]() 281 return True
282
283 - def play_animation(self):
284 """Play the animation with a generator and by sending 285 a request to the GTK server""" 286 if self.simu != None: 287 frame_step = self.spin_button.get_value_as_int() 288 mess = "The turbine will be played with a frame step of %i" 289 self.status_bar.write(mess % frame_step) 290 self.simu.set_frame_step(frame_step) 291 animation = self.animate_with_generators() 292 self.animation_id = gobject.idle_add(animation.next) 293 else: 294 self.status_bar.write("No simulation loaded") 295 self.button["stop"].set_active(True)
296
297 - def stop_animation(self):
298 """Stop the animation and destroy the current process 299 is any""" 300 if self.simu != None: 301 if self.animation_id != None: 302 gobject.source_remove(self.animation_id) 303 self.animation_id = None 304 self.status_bar.write("Animation stopped")
305
306 - def set_first_frame(self):
307 """Set the first simulation frame""" 308 if self.simu != None: 309 if self.animation_id != None: 310 self.stop_animation() 311 self.current_frame_id = 0 312 self.set_simulation_at_current_frame() 313 self.status_bar.write("First frame set") 314 else: 315 self.status_bar.write("No simulation loaded") 316 self.button["stop"].set_active(True)
317
318 - def set_last_frame(self):
319 """Set the last simulation frame""" 320 if self.simu != None: 321 if self.animation_id != None: 322 self.stop_animation() 323 self.current_frame_id = self.simu.nb_frames - 1 324 self.set_simulation_at_current_frame() 325 self.status_bar.write("Last frame set") 326 else: 327 self.status_bar.write("No simulation loaded") 328 self.button["stop"].set_active(True)
329
331 """Set the simulation at the C{current_frame_id}""" 332 self.simu.set_description(self.current_frame_id) 333 self.time_label.set_label("%5.2f s" % \ 334 self.simu.results.time[self.current_frame_id]) 335 self.frame_scale.set_value(self.current_frame_id) 336 self.win_interactor.Render() 337 # For other callbacks (Ex: plotting the pressure) 338 self.simu.current_frame_id = self.current_frame_id
339
340 - def _get_frames_ids(self):
341 """Return the list of frames to see""" 342 frames_ids = range(self.current_frame_id, 343 self.simu.nb_frames, 344 self.spin_button.get_value_as_int()) 345 # In case the interval is too big, the last frame id will be 346 # played for sure. 347 frames_ids += [self.simu.nb_frames - 1] 348 return frames_ids
349
350 - def animate_with_generators(self):
351 """Animate the simulation as a generator object""" 352 for self.current_frame_id in self._get_frames_ids(): 353 self.set_simulation_at_current_frame() 354 self.win_interactor.Render() 355 yield True 356 self.button["stop"].set_active(True) 357 yield False
358 359
360 -class Manager:
361 """A class managing the wind turbine simulation instance with 362 the VTK area and the GTK area (or interface). 363 364 For the MBDyn interfacing, the VTK area is defined by 365 C{GtkVtkArea} from the L{windSimSuite.interface.vtk_area} module, 366 displaying the turbine in 3D. The GTK area is defined by the L{TreeView}, 367 displaying the turbine with GTK cells on which the user can right click. 368 369 However the GTK area is doing more as figures can also be output from 370 the Matplotlib interface. The C{Manager} is also taking care of the notebook, 371 displaying a new page when a new figure is drawn, thanks to 372 the C{FigureManager} from the L{windSimSuite.interface.matplotlib_manager}. 373 The C{Manager} can also hide the VTK area, contained in the notebook 374 if nothing is available for a 3D view.""" 375
376 - def __init__(self, widgets):
377 self.status_bar = StatusBar(widgets["statusbar"]) 378 379 # VTK area 380 self.vtk_area = GtkVtkArea(self.status_bar) 381 self.vtk_area.set_on_right_click(self.show_menu) 382 self.vtk_area.set_on_motion_notify_event(self.track_actors) 383 self.win_interactor = self.vtk_area.win_interactor 384 385 # GTK area 386 387 self.treeview = {} 388 for key, title in zip(["turbine", "mbdyn"], 389 ["Wind turbine", "MBDyn objects"]): 390 tvw = TreeView(self.status_bar, title) 391 tvw.connect_button_press_event(self.show_menu) 392 tvw.show() 393 self.treeview[key] = tvw 394 395 self.notebook = widgets["notebook"] 396 self.fig_manager = FigureManager(self.notebook, 397 self.status_bar) 398 self.fig_manager.set_window_ref(widgets["application"]) 399 400 self.menu_table = MENU_TABLE 401 for menu in MENU_TABLE.values(): 402 for prop_key in menu.properties["vtk"]: 403 menu.connect_with_key(prop_key, 404 self.set_obj_boolean_for_vtk) 405 for prop_key in menu.properties["pylab"]: 406 menu.connect_with_key(prop_key, 407 self.plot_obj) 408 409 self.simu = None 410 self.obj = None 411 412 self.anim = AnimationToolbar(self.win_interactor, 413 self.status_bar, 414 widgets) 415 # For loading the simulation, not yet implemented 416 self.progress_bar = widgets["progressbar"] 417 self.progress_bar.hide() 418 self.task_box = widgets["task_box"] 419 420 self.absolute_ref = vtkReferenceFrame() 421 unit_arrow = ArrowMapper() 422 self.absolute_ref.set_arrow(unit_arrow) 423 self.scale_table = ScaleTable(widgets, self.win_interactor) 424 self.scale_table.add_vector(unit_arrow) 425 self.show_absolute_ref()
426
427 - def show_menu(self, area, event):
428 """Show the action menu. 429 Called when button-press-event, with event=3, 430 is emitted by the GtkVTKSelector area or one 431 of the treeviews. 432 There is still a problem with True/False for the 433 VTK area?""" 434 obj = area.get_object(event) 435 if obj == None: 436 return False 437 else: 438 self.obj = obj 439 menu = self.menu_table[obj.menu_type] 440 menu.set_for(obj) 441 menu.custom_popup(event) 442 return True
443
444 - def track_actors(self, vtk_area, event):
445 """Track the VTK actors. 446 Act on 'motion_notify_event' emitted by the GtkVTKSelector""" 447 obj = self.vtk_area.get_object(event) 448 if obj == None: 449 self.vtk_area.remove_items() 450 else: 451 self.obj = obj 452 self.vtk_area.add_item(self.obj) 453 self.win_interactor.Render()
454
455 - def set_obj_boolean_for_vtk(self, gtk_check_item, key):
456 """Update the properties of an object for the VTK area. 457 458 The action can be called by right click on the GTK area 459 (in the Treeview) or from the VTK area. 460 """ 461 if gtk_check_item.get_active(): 462 if not self.obj.boolean[key]: 463 status = self.obj.activate(key, 464 self.anim.current_frame_id, 465 self.vtk_area) 466 self.status_bar.write(status) 467 else: 468 if self.obj.boolean[key]: 469 status = self.obj.desactivate(key, 470 self.vtk_area) 471 self.status_bar.write(status) 472 self.win_interactor.Render() 473 #event = gtk.gdk.Event(gtk.gdk.BUTTON_PRESS) 474 #gtk_check_item.emit("button_press_event", event) 475 return False
476
477 - def plot_obj(self, gtk_item, key):
478 """Update the properties of an object for the VTK area. 479 480 The action can be called by right click on the GTK area 481 (in the Treeview) or from the VTK area. 482 """ 483 status = self.fig_manager.plot(key, self.obj, self.simu) 484 self.status_bar.write(status) 485 return False
486
487 - def load_simulation_with_generators(self, filename):
488 """Load the simulation by a generator object. This 489 method does not work as expected, there is still lot of 490 work for the loading process of every object.""" 491 self.task_box.show() 492 yield True 493 if self.simu != None: 494 self.vtk_area.clean() 495 pyf = PyMBDynFile(filename) 496 yield True 497 self.simu = pyf.simulations[0] 498 pyf.close() 499 yield True 500 self.simu.turbine.display_on(self.treeview["turbine"]) 501 yield True 502 self.simu.display_on(self.treeview["mbdyn"]) 503 yield True 504 self.simu.set_scale_table(self.scale_table) 505 self.simu.create_vtk_actors() 506 self.absolute_ref.set_arrow(self.simu.unit_arrow) 507 yield True 508 self.simu.add_actors_to_select(self.vtk_area) 509 yield True 510 self.simu.init_from_loaded_file() 511 yield True 512 self.anim.set_simulation(self.simu) 513 self.task_box.hide() 514 yield False
515
516 - def show_absolute_ref(self):
517 """Show the absolute reference frame""" 518 for axe in self.absolute_ref.axes: 519 self.vtk_area.renderer.AddActor(axe.actor) 520 self.win_interactor.Render()
521
522 - def hide_absolute_ref(self):
523 """Hide the absolute reference frame""" 524 for axe in self.absolute_ref.axes: 525 self.vtk_area.renderer.RemoveActor(axe.actor) 526 self.win_interactor.Render()
527