A Discrete-Event Network Simulator
API
core.py
Go to the documentation of this file.
1 # -*- Mode: python; coding: utf-8 -*-
2 from ctypes import c_double
3 
4 LAYOUT_ALGORITHM = "neato" # ['neato'|'dot'|'twopi'|'circo'|'fdp'|'nop']
5 REPRESENT_CHANNELS_AS_NODES = 1
6 DEFAULT_NODE_SIZE = 1.0 # default node size in meters
7 DEFAULT_TRANSMISSIONS_MEMORY = (
8  5 # default number of of past intervals whose transmissions are remembered
9 )
10 BITRATE_FONT_SIZE = 10
11 
12 # internal constants, normally not meant to be changed
13 SAMPLE_PERIOD = 0.1
14 PRIORITY_UPDATE_MODEL = -100
15 PRIORITY_UPDATE_VIEW = 200
16 
17 import platform
18 import warnings
19 
20 if platform.system() == "Windows":
21  SHELL_FONT = "Lucida Console 9"
22 else:
23  SHELL_FONT = "Luxi Mono 10"
24 
25 import math
26 import os
27 import sys
28 
29 try:
30  import threading
31 except ImportError:
32  import dummy_threading as threading
33 
34 try:
35  import pygraphviz
36 except ImportError:
37  print("Pygraphviz is required by the visualizer module and could not be found")
38  exit(1)
39 
40 try:
41  import cairo
42 except ImportError:
43  print("Pycairo is required by the visualizer module and could not be found")
44  exit(1)
45 
46 try:
47  import gi
48 except ImportError:
49  print("PyGObject is required by the visualizer module and could not be found")
50  exit(1)
51 
52 try:
53  import svgitem
54 except ImportError:
55  svgitem = None
56 
57 try:
58  gi.require_version("GooCanvas", "2.0")
59  gi.require_version("Gtk", "3.0")
60  gi.require_version("Gdk", "3.0")
61  gi.require_foreign("cairo")
62  from gi.repository import Gdk, GLib, GObject, GooCanvas, Gtk, Pango
63 
64  from . import hud
65 except ImportError as e:
66  _import_error = e
67 else:
68  _import_error = None
69 
70 try:
71  from . import ipython_view
72 except ImportError:
73  ipython_view = None
74 
75 from .base import (
76  PIXELS_PER_METER,
77  InformationWindow,
78  Link,
79  PyVizObject,
80  load_plugins,
81  lookup_netdevice_traits,
82  plugins,
83  register_plugin,
84  transform_distance_canvas_to_simulation,
85  transform_distance_simulation_to_canvas,
86  transform_point_canvas_to_simulation,
87  transform_point_simulation_to_canvas,
88 )
89 
90 PI_OVER_2 = math.pi / 2
91 PI_TIMES_2 = math.pi * 2
92 
93 
94 
96 
134 
135 
138  __gsignals__ = {
139  "query-extra-tooltip-info": (GObject.SignalFlags.RUN_LAST, None, (object,)),
140  }
141 
142  def __init__(self, visualizer, node_index):
143  """! Initialize function.
144  @param self The object pointer.
145  @param visualizer visualizer object
146  @param node_index node index
147  """
148  super(Node, self).__init__()
149 
150  self.visualizervisualizer = visualizer
151  self.node_indexnode_index = node_index
152  self.canvas_itemcanvas_item = GooCanvas.CanvasEllipse()
153  self.canvas_itemcanvas_item.pyviz_object = self
154  self.linkslinks = []
155  self._has_mobility_has_mobility = None
156  self._selected_selected = False
157  self._highlighted_highlighted = False
158  self._color_color = 0x808080FF
159  self._size_size = DEFAULT_NODE_SIZE
160  self.canvas_itemcanvas_item.connect("enter-notify-event", self.on_enter_notify_eventon_enter_notify_event)
161  self.canvas_itemcanvas_item.connect("leave-notify-event", self.on_leave_notify_eventon_leave_notify_event)
162  self.menumenu = None
163  self.svg_itemsvg_item = None
164  self.svg_align_xsvg_align_x = None
165  self.svg_align_ysvg_align_y = None
166  self._label_label = None
167  self._label_canvas_item_label_canvas_item = None
168 
169  self._update_appearance_update_appearance() # call this last
170 
171  def set_svg_icon(self, file_base_name, width=None, height=None, align_x=0.5, align_y=0.5):
172  """!
173  Set a background SVG icon for the node.
174 
175  @param file_base_name: base file name, including .svg
176  extension, of the svg file. Place the file in the folder
177  src/contrib/visualizer/resource.
178 
179  @param width: scale to the specified width, in meters
180  @param height: scale to the specified height, in meters
181 
182  @param align_x: horizontal alignment of the icon relative to
183  the node position, from 0 (icon fully to the left of the node)
184  to 1.0 (icon fully to the right of the node)
185 
186  @param align_y: vertical alignment of the icon relative to the
187  node position, from 0 (icon fully to the top of the node) to
188  1.0 (icon fully to the bottom of the node)
189 
190  @return a ValueError exception if invalid dimensions.
191 
192  """
193  if width is None and height is None:
194  raise ValueError("either width or height must be given")
195  rsvg_handle = svgitem.rsvg_handle_factory(file_base_name)
196  x = self.canvas_itemcanvas_item.props.center_x
197  y = self.canvas_itemcanvas_item.props.center_y
198  self.svg_itemsvg_item = svgitem.SvgItem(x, y, rsvg_handle)
199  self.svg_itemsvg_item.props.parent = self.visualizervisualizer.canvas.get_root_item()
200  self.svg_itemsvg_item.props.pointer_events = GooCanvas.CanvasPointerEvents.NONE
201  self.svg_itemsvg_item.lower(None)
202  self.svg_itemsvg_item.props.visibility = GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD
203  if width is not None:
204  self.svg_itemsvg_item.props.width = transform_distance_simulation_to_canvas(width)
205  if height is not None:
206  self.svg_itemsvg_item.props.height = transform_distance_simulation_to_canvas(height)
207 
208  # threshold1 = 10.0/self.svg_item.props.height
209  # threshold2 = 10.0/self.svg_item.props.width
210  # self.svg_item.props.visibility_threshold = min(threshold1, threshold2)
211 
212  self.svg_align_xsvg_align_x = align_x
213  self.svg_align_ysvg_align_y = align_y
214  self._update_svg_position_update_svg_position(x, y)
215  self._update_appearance_update_appearance()
216 
217  def set_label(self, label):
218  """!
219  Set a label for the node.
220 
221  @param self: class object.
222  @param label: label to set
223 
224  @return: an exception if invalid parameter.
225  """
226  assert isinstance(label, basestring)
227  self._label_label = label
228  self._update_appearance_update_appearance()
229 
230  def _update_svg_position(self, x, y):
231  """!
232  Update svg position.
233 
234  @param self: class object.
235  @param x: x position
236  @param y: y position
237  @return none
238  """
239  w = self.svg_itemsvg_item.width
240  h = self.svg_itemsvg_item.height
241  self.svg_itemsvg_item.set_properties(
242  x=(x - (1 - self.svg_align_xsvg_align_x) * w), y=(y - (1 - self.svg_align_ysvg_align_y) * h)
243  )
244 
245  def tooltip_query(self, tooltip):
246  """!
247  Query tooltip.
248 
249  @param self: class object.
250  @param tooltip: tooltip
251  @return none
252  """
253  self.visualizervisualizer.simulation.lock.acquire()
254  try:
255  ns3_node = ns.NodeList.GetNode(self.node_indexnode_index)
256  ipv4 = ns.cppyy.gbl.getNodeIpv4(ns3_node)
257  ipv6 = ns.cppyy.gbl.getNodeIpv6(ns3_node)
258 
259  name = "<b><u>Node %i</u></b>" % self.node_indexnode_index
260  node_name = ns.Names.FindName(ns3_node)
261  if len(node_name) != 0:
262  name += " <b>(" + node_name + ")</b>"
263 
264  lines = [name]
265  lines.append("")
266 
267  self.emit("query-extra-tooltip-info", lines)
268 
269  mob = ns.cppyy.gbl.hasMobilityModel(ns3_node)
270  if mob:
271  mobility_model_name = ns.cppyy.gbl.getMobilityModelName(ns3_node)
272  lines.append(
273  " <b>Mobility Model</b>: %s" % ns.cppyy.gbl.getMobilityModelName(ns3_node)
274  )
275 
276  for devI in range(ns3_node.GetNDevices()):
277  lines.append("")
278  lines.append(" <u>NetDevice %i:</u>" % devI)
279  dev = ns3_node.GetDevice(devI)
280  name = ns.Names.FindName(dev)
281  if name:
282  lines.append(" <b>Name:</b> %s" % name)
283  devname = dev.GetInstanceTypeId().GetName()
284  lines.append(" <b>Type:</b> %s" % devname)
285 
286  if ipv4 is not None:
287  ipv4_idx = ipv4.GetInterfaceForDevice(dev)
288  if ipv4_idx != -1:
289  addresses = [
290  "%s/%s"
291  % (
292  ipv4.GetAddress(ipv4_idx, i).GetLocal(),
293  ipv4.GetAddress(ipv4_idx, i).GetMask(),
294  )
295  for i in range(ipv4.GetNAddresses(ipv4_idx))
296  ]
297  lines.append(" <b>IPv4 Addresses:</b> %s" % "; ".join(addresses))
298 
299  if ipv6 is not None:
300  ipv6_idx = ipv6.GetInterfaceForDevice(dev)
301  if ipv6_idx != -1:
302  addresses = [
303  "%s/%s"
304  % (
305  ipv6.GetAddress(ipv6_idx, i).GetAddress(),
306  ipv6.GetAddress(ipv6_idx, i).GetPrefix(),
307  )
308  for i in range(ipv6.GetNAddresses(ipv6_idx))
309  ]
310  lines.append(" <b>IPv6 Addresses:</b> %s" % "; ".join(addresses))
311 
312  lines.append(" <b>MAC Address:</b> %s" % (dev.GetAddress(),))
313 
314  tooltip.set_markup("\n".join(lines))
315  finally:
316  self.visualizervisualizer.simulation.lock.release()
317 
318  def on_enter_notify_event(self, view, target, event):
319  """!
320  On Enter event handle.
321 
322  @param self: class object.
323  @param view: view
324  @param target: target
325  @param event: event
326  @return none
327  """
328 
329 
330  self.highlightedhighlighted = True
331 
332  def on_leave_notify_event(self, view, target, event):
333  """!
334  On Leave event handle.
335 
336  @param self: class object.
337  @param view: view
338  @param target: target
339  @param event: event
340  @return none
341  """
342  self.highlightedhighlighted = False
343 
344  def _set_selected(self, value):
345  """!
346  Set selected function.
347 
348  @param self: class object.
349  @param value: selected value
350  @return none
351  """
352  self._selected_selected = value
353  self._update_appearance_update_appearance()
354 
355  def _get_selected(self):
356  """!
357  Get selected function.
358 
359  @param self: class object.
360  @return selected status
361  """
362  return self._selected_selected
363 
364  selected = property(_get_selected, _set_selected)
365 
366  def _set_highlighted(self, value):
367  """!
368  Set highlighted function.
369 
370  @param self: class object.
371  @param value: selected value
372  @return none
373  """
374  self._highlighted_highlighted = value
375  self._update_appearance_update_appearance()
376 
377  def _get_highlighted(self):
378  """!
379  Get highlighted function.
380 
381  @param self: class object.
382  @return highlighted status
383  """
384  return self._highlighted_highlighted
385 
386  highlighted = property(_get_highlighted, _set_highlighted)
387 
388  def set_size(self, size):
389  """!
390  Set size function.
391 
392  @param self: class object.
393  @param size: selected size
394  @return none
395  """
396  self._size_size = size
397  self._update_appearance_update_appearance()
398 
400  """!
401  Update the node aspect to reflect the selected/highlighted state
402 
403  @param self: class object.
404  @return none
405  """
406 
408  if self.svg_itemsvg_item is not None:
409  alpha = 0x80
410  else:
411  alpha = 0xFF
412  fill_color_rgba = (self._color_color & 0xFFFFFF00) | alpha
413  self.canvas_itemcanvas_item.set_properties(
414  radius_x=size, radius_y=size, fill_color_rgba=fill_color_rgba
415  )
416  if self._selected_selected:
417  line_width = size * 0.3
418  else:
419  line_width = size * 0.15
420  if self.highlightedhighlighted:
421  stroke_color = "yellow"
422  else:
423  stroke_color = "black"
424  self.canvas_itemcanvas_item.set_properties(line_width=line_width, stroke_color=stroke_color)
425 
426  if self._label_label is not None:
427  if self._label_canvas_item_label_canvas_item is None:
428  self._label_canvas_item_label_canvas_item = GooCanvas.CanvasText(
429  visibility_threshold=0.5,
430  font="Sans Serif 10",
431  fill_color_rgba=0x808080FF,
432  alignment=Pango.Alignment.CENTER,
433  anchor=GooCanvas.CanvasAnchorType.N,
434  parent=self.visualizervisualizer.canvas.get_root_item(),
435  pointer_events=GooCanvas.CanvasPointerEvents.NONE,
436  )
437  self._label_canvas_item_label_canvas_item.lower(None)
438 
439  self._label_canvas_item_label_canvas_item.set_properties(
440  visibility=GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD, text=self._label_label
441  )
442  self._update_position_update_position()
443 
444  def set_position(self, x, y):
445  """!
446  Set position function.
447 
448  @param self: class object.
449  @param x: x position
450  @param y: y position
451  @return none
452  """
453  self.canvas_itemcanvas_item.set_property("center_x", x)
454  self.canvas_itemcanvas_item.set_property("center_y", y)
455  if self.svg_itemsvg_item is not None:
456  self._update_svg_position_update_svg_position(x, y)
457 
458  for link in self.linkslinks:
459  link.update_points()
460 
461  if self._label_canvas_item_label_canvas_item is not None:
462  self._label_canvas_item_label_canvas_item.set_properties(x=x, y=(y + self._size_size * 3))
463 
464  # If the location of the point is now beyond the bounds of the
465  # canvas then those bounds now need to be increased
466  try:
467  bounds = self.visualizervisualizer.canvas.get_bounds()
468 
469  (min_x, min_y, max_x, max_y) = bounds
470 
471  min_x = min(x, min_x)
472  min_y = min(y, min_y)
473  max_x = max(x, max_x)
474  max_y = max(y, max_y)
475 
476  new_bounds = (min_x, min_y, max_x, max_y)
477 
478  if new_bounds != bounds:
479  self.visualizervisualizer.canvas.set_bounds(*new_bounds)
480  except TypeError:
481  # bug 2969: GooCanvas.Canvas.get_bounds() inconsistency
482  pass
483 
484  def get_position(self):
485  """!
486  Get position function.
487 
488  @param self: class object.
489  @return x and y position
490  """
491  return (
492  self.canvas_itemcanvas_item.get_property("center_x"),
493  self.canvas_itemcanvas_item.get_property("center_y"),
494  )
495 
496  def _update_position(self):
497  """!
498  Update position function.
499 
500  @param self: class object.
501  @return none
502  """
503  x, y = self.get_positionget_position()
504  self.set_positionset_position(x, y)
505 
506  def set_color(self, color):
507  """!
508  Set color function.
509 
510  @param self: class object.
511  @param color: color to set.
512  @return none
513  """
514  if isinstance(color, str):
515  color = Gdk.color_parse(color)
516  color = (
517  ((color.red >> 8) << 24)
518  | ((color.green >> 8) << 16)
519  | ((color.blue >> 8) << 8)
520  | 0xFF
521  )
522  self._color_color = color
523  self._update_appearance_update_appearance()
524 
525  def add_link(self, link):
526  """!
527  Add link function.
528 
529  @param self: class object.
530  @param link: link to add.
531  @return none
532  """
533  assert isinstance(link, Link)
534  self.linkslinks.append(link)
535 
536  def remove_link(self, link):
537  """!
538  Remove link function.
539 
540  @param self: class object.
541  @param link: link to add.
542  @return none
543  """
544  assert isinstance(link, Link)
545  self.linkslinks.remove(link)
546 
547  @property
548  def has_mobility(self):
549  """!
550  Has mobility function.
551 
552  @param self: class object.
553  @return modility option
554  """
555  if self._has_mobility_has_mobility is None:
556  node = ns.NodeList.GetNode(self.node_indexnode_index)
557  self._has_mobility_has_mobility = ns.cppyy.gbl.hasMobilityModel(node)
558  return self._has_mobility_has_mobility
559 
560 
561 
563 
570  def __init__(self, channel):
571  """!
572  Initializer function.
573 
574  @param self: class object.
575  @param channel: channel.
576  """
577  self.channelchannel = channel
578  self.canvas_itemcanvas_item = GooCanvas.CanvasEllipse(
579  radius_x=30,
580  radius_y=30,
581  fill_color="white",
582  stroke_color="grey",
583  line_width=2.0,
584  line_dash=GooCanvas.CanvasLineDash.newv([10.0, 10.0]),
585  visibility=GooCanvas.CanvasItemVisibility.VISIBLE,
586  )
587  self.canvas_itemcanvas_item.pyviz_object = self
588  self.linkslinks = []
589 
590  def set_position(self, x, y):
591  """!
592  Initializer function.
593 
594  @param self: class object.
595  @param x: x position.
596  @param y: y position.
597  @return
598  """
599  self.canvas_itemcanvas_item.set_property("center_x", x)
600  self.canvas_itemcanvas_item.set_property("center_y", y)
601 
602  for link in self.linkslinks:
603  link.update_points()
604 
605  def get_position(self):
606  """!
607  Initializer function.
608 
609  @param self: class object.
610  @return x / y position.
611  """
612  return (
613  self.canvas_itemcanvas_item.get_property("center_x"),
614  self.canvas_itemcanvas_item.get_property("center_y"),
615  )
616 
617 
618 
620 
627  def __init__(self, node1, node2):
628  """!
629  Initializer function.
630 
631  @param self: class object.
632  @param node1: class object.
633  @param node2: class object.
634  """
635  assert isinstance(node1, Node)
636  assert isinstance(node2, (Node, Channel))
637  self.node1node1 = node1
638  self.node2node2 = node2
639  self.canvas_itemcanvas_item = GooCanvas.CanvasPath(line_width=1.0, stroke_color="black")
640  self.canvas_itemcanvas_item.pyviz_object = self
641  self.node1node1.links.append(self)
642  self.node2node2.links.append(self)
643 
644  def update_points(self):
645  """!
646  Update points function.
647 
648  @param self: class object.
649  @return none
650  """
651  pos1_x, pos1_y = self.node1node1.get_position()
652  pos2_x, pos2_y = self.node2node2.get_position()
653  self.canvas_itemcanvas_item.set_property("data", "M %r %r L %r %r" % (pos1_x, pos1_y, pos2_x, pos2_y))
654 
655 
656 
657 class SimulationThread(threading.Thread):
658 
672  def __init__(self, viz):
673  """!
674  Initializer function.
675 
676  @param self: class object.
677  @param viz: class object.
678  """
679  super(SimulationThread, self).__init__()
680  assert isinstance(viz, Visualizer)
681  self.vizviz = viz # Visualizer object
682  self.locklock = threading.Lock()
683  self.gogo = threading.Event()
684  self.gogo.clear()
685  self.target_timetarget_time = 0 # in seconds
686  self.quitquit = False
687  self.sim_helpersim_helper = ns.PyViz()
688  self.pause_messagespause_messages = []
689 
690  def set_nodes_of_interest(self, nodes):
691  """!
692  Set nodes of interest function.
693 
694  @param self: class object.
695  @param nodes: class object.
696  @return
697  """
698  self.locklock.acquire()
699  try:
700  self.sim_helpersim_helper.SetNodesOfInterest(nodes)
701  finally:
702  self.locklock.release()
703 
704  def run(self):
705  """!
706  Initializer function.
707 
708  @param self: class object.
709  @return none
710  """
711  while not self.quitquit:
712  # print "sim: Wait for go"
713  self.gogo.wait() # wait until the main (view) thread gives us the go signal
714  self.gogo.clear()
715  if self.quitquit:
716  break
717  # self.go.clear()
718  # print "sim: Acquire lock"
719  self.locklock.acquire()
720  try:
721  if 0:
723  self.vizviz.play_button.set_sensitive(False)
724  break
725  # print "sim: Current time is %f; Run until: %f" % (ns3.Simulator.Now ().GetSeconds (), self.target_time)
726  # if ns3.Simulator.Now ().GetSeconds () > self.target_time:
727  # print "skipping, model is ahead of view!"
728  self.sim_helpersim_helper.SimulatorRunUntil(ns.Seconds(self.target_timetarget_time))
729  # print "sim: Run until ended at current time: ", ns3.Simulator.Now ().GetSeconds ()
730  self.pause_messagespause_messages.extend(self.sim_helpersim_helper.GetPauseMessages())
731  GLib.idle_add(self.vizviz.update_model, priority=PRIORITY_UPDATE_MODEL)
732  # print "sim: Run until: ", self.target_time, ": finished."
733  finally:
734  self.locklock.release()
735  # print "sim: Release lock, loop."
736 
737 
738 
740 
746 
747 
748  __slots__ = []
749 
750 
751 ShowTransmissionsMode.ALL = ShowTransmissionsMode()
752 ShowTransmissionsMode.NONE = ShowTransmissionsMode()
753 ShowTransmissionsMode.SELECTED = ShowTransmissionsMode()
754 
755 
756 
757 class Visualizer(GObject.GObject):
758 
760  INSTANCE = None
761 
762  if _import_error is None:
763  __gsignals__ = {
764  # signal emitted whenever a right-click-on-node popup menu is being constructed
765  "populate-node-menu": (
766  GObject.SignalFlags.RUN_LAST,
767  None,
768  (
769  object,
770  Gtk.Menu,
771  ),
772  ),
773  # signal emitted after every simulation period (SAMPLE_PERIOD seconds of simulated time)
774  # the simulation lock is acquired while the signal is emitted
775  "simulation-periodic-update": (GObject.SignalFlags.RUN_LAST, None, ()),
776  # signal emitted right after the topology is scanned
777  "topology-scanned": (GObject.SignalFlags.RUN_LAST, None, ()),
778  # signal emitted when it's time to update the view objects
779  "update-view": (GObject.SignalFlags.RUN_LAST, None, ()),
780  }
781 
782  def __init__(self):
783  """!
784  Initializer function.
785 
786  @param self: class object.
787  @return none
788  """
789  assert Visualizer.INSTANCE is None
790  Visualizer.INSTANCE = self
791  super(Visualizer, self).__init__()
792  self.nodes = {} # node index -> Node
793  self.channels = {} # id(ns3.Channel) -> Channel
794  self.window = None # toplevel window
795  self.canvas = None # GooCanvas.Canvas
796  self.time_label = None # Gtk.Label
797  self.play_button = None # Gtk.ToggleButton
798  self.zoom = None # Gtk.Adjustment
799  self._scrolled_window = None # Gtk.ScrolledWindow
800 
801  self.links_group = GooCanvas.CanvasGroup()
802  self.channels_group = GooCanvas.CanvasGroup()
803  self.nodes_group = GooCanvas.CanvasGroup()
804 
805  self._update_timeout_id = None
806  self.simulation = SimulationThread(self)
807  self.selected_node = None # node currently selected
808  self.speed = 1.0
809  self.information_windows = []
810  self._transmission_arrows = []
811  self._last_transmissions = []
812  self._drop_arrows = []
813  self._last_drops = []
814  self._show_transmissions_mode = None
815  self.set_show_transmissions_mode(ShowTransmissionsMode.ALL)
816  self._panning_state = None
817  self.node_size_adjustment = None
818  self.transmissions_smoothing_adjustment = None
819  self.sample_period = SAMPLE_PERIOD
820  self.node_drag_state = None
821  self.follow_node = None
822  self.shell_window = None
823 
824  self.create_gui()
825 
826  for plugin in plugins:
827  plugin(self)
828 
829  def set_show_transmissions_mode(self, mode):
830  """!
831  Set show transmission mode.
832 
833  @param self: class object.
834  @param mode: mode to set.
835  @return none
836  """
837  assert isinstance(mode, ShowTransmissionsMode)
838  self._show_transmissions_mode = mode
839  if self._show_transmissions_mode == ShowTransmissionsMode.ALL:
840  self.simulation.set_nodes_of_interest(list(range(ns.NodeList.GetNNodes())))
841  elif self._show_transmissions_mode == ShowTransmissionsMode.NONE:
842  self.simulation.set_nodes_of_interest([])
843  elif self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
844  if self.selected_node is None:
845  self.simulation.set_nodes_of_interest([])
846  else:
847  self.simulation.set_nodes_of_interest([self.selected_node.node_index])
848 
849  def _create_advanced_controls(self):
850  """!
851  Create advanced controls.
852 
853  @param self: class object.
854  @return expander
855  """
856  expander = Gtk.Expander.new("Advanced")
857  expander.show()
858 
859  main_vbox = GObject.new(Gtk.VBox, border_width=8, visible=True)
860  expander.add(main_vbox)
861 
862  main_hbox1 = GObject.new(Gtk.HBox, border_width=8, visible=True)
863  main_vbox.pack_start(main_hbox1, True, True, 0)
864 
865  show_transmissions_group = GObject.new(
866  Gtk.HeaderBar, title="Show transmissions", visible=True
867  )
868  main_hbox1.pack_start(show_transmissions_group, False, False, 8)
869 
870  vbox = Gtk.VBox(homogeneous=True, spacing=4)
871  vbox.show()
872  show_transmissions_group.add(vbox)
873 
874  all_nodes = Gtk.RadioButton.new(None)
875  all_nodes.set_label("All nodes")
876  all_nodes.set_active(True)
877  all_nodes.show()
878  vbox.add(all_nodes)
879 
880  selected_node = Gtk.RadioButton.new_from_widget(all_nodes)
881  selected_node.show()
882  selected_node.set_label("Selected node")
883  selected_node.set_active(False)
884  vbox.add(selected_node)
885 
886  no_node = Gtk.RadioButton.new_from_widget(all_nodes)
887  no_node.show()
888  no_node.set_label("Disabled")
889  no_node.set_active(False)
890  vbox.add(no_node)
891 
892  def toggled(radio):
893  if radio.get_active():
894  self.set_show_transmissions_mode(ShowTransmissionsMode.ALL)
895 
896  all_nodes.connect("toggled", toggled)
897 
898  def toggled(radio):
899  if radio.get_active():
900  self.set_show_transmissions_mode(ShowTransmissionsMode.NONE)
901 
902  no_node.connect("toggled", toggled)
903 
904  def toggled(radio):
905  if radio.get_active():
906  self.set_show_transmissions_mode(ShowTransmissionsMode.SELECTED)
907 
908  selected_node.connect("toggled", toggled)
909 
910  # -- misc settings
911  misc_settings_group = GObject.new(Gtk.HeaderBar, title="Misc Settings", visible=True)
912  main_hbox1.pack_start(misc_settings_group, False, False, 8)
913  settings_hbox = GObject.new(Gtk.HBox, border_width=8, visible=True)
914  misc_settings_group.add(settings_hbox)
915 
916  # --> node size
917  vbox = GObject.new(Gtk.VBox, border_width=0, visible=True)
918  scale = GObject.new(Gtk.HScale, visible=True, digits=2)
919  vbox.pack_start(scale, True, True, 0)
920  vbox.pack_start(GObject.new(Gtk.Label, label="Node Size", visible=True), True, True, 0)
921  settings_hbox.pack_start(vbox, False, False, 6)
922  self.node_size_adjustment = scale.get_adjustment()
923 
924  def node_size_changed(adj):
925  for node in self.nodes.values():
926  node.set_size(adj.get_value())
927 
928  self.node_size_adjustment.connect("value-changed", node_size_changed)
929  self.node_size_adjustment.set_lower(0.01)
930  self.node_size_adjustment.set_upper(20)
931  self.node_size_adjustment.set_step_increment(0.1)
932  self.node_size_adjustment.set_value(DEFAULT_NODE_SIZE)
933 
934  # --> transmissions smooth factor
935  vbox = GObject.new(Gtk.VBox, border_width=0, visible=True)
936  scale = GObject.new(Gtk.HScale, visible=True, digits=1)
937  vbox.pack_start(scale, True, True, 0)
938  vbox.pack_start(
939  GObject.new(Gtk.Label, label="Tx. Smooth Factor (s)", visible=True), True, True, 0
940  )
941  settings_hbox.pack_start(vbox, False, False, 6)
942  self.transmissions_smoothing_adjustment = scale.get_adjustment()
943  adj = self.transmissions_smoothing_adjustment
944  adj.set_lower(0.1)
945  adj.set_upper(10)
946  adj.set_step_increment(0.1)
947  adj.set_value(DEFAULT_TRANSMISSIONS_MEMORY * 0.1)
948 
949  return expander
950 
951 
952  class _PanningState(object):
953 
955  __slots__ = ["initial_mouse_pos", "initial_canvas_pos", "motion_signal"]
956 
957  def _begin_panning(self, widget, event):
958  """!
959  Set show trnamission mode.
960 
961  @param self: class object.
962  @param mode: mode to set.
963  @return none
964  """
965  display = self.canvas.get_window().get_display()
966  cursor = Gdk.Cursor.new_for_display(display, Gdk.CursorType.FLEUR)
967  self.canvas.get_window().set_cursor(cursor)
968  self._panning_state = self._PanningState()
969  pos = widget.get_window().get_device_position(event.device)
970  self._panning_state.initial_mouse_pos = (pos.x, pos.y)
971  x = self._scrolled_window.get_hadjustment().get_value()
972  y = self._scrolled_window.get_vadjustment().get_value()
973  self._panning_state.initial_canvas_pos = (x, y)
974  self._panning_state.motion_signal = self.canvas.connect(
975  "motion-notify-event", self._panning_motion
976  )
977 
978  def _end_panning(self, event):
979  """!
980  End panning function.
981 
982  @param self: class object.
983  @param event: active event.
984  @return none
985  """
986  if self._panning_state is None:
987  return
988  self.canvas.get_window().set_cursor(None)
989  self.canvas.disconnect(self._panning_state.motion_signal)
990  self._panning_state = None
991 
992  def _panning_motion(self, widget, event):
993  """!
994  Panning motion function.
995 
996  @param self: class object.
997  @param widget: widget.
998  @param event: event.
999  @return true if successful
1000  """
1001  assert self._panning_state is not None
1002  if event.is_hint:
1003  pos = widget.get_window().get_device_position(event.device)
1004  x, y = pos.x, pos.y
1005  else:
1006  x, y = event.x, event.y
1007 
1008  hadj = self._scrolled_window.get_hadjustment()
1009  vadj = self._scrolled_window.get_vadjustment()
1010  mx0, my0 = self._panning_state.initial_mouse_pos
1011  cx0, cy0 = self._panning_state.initial_canvas_pos
1012 
1013  dx = x - mx0
1014  dy = y - my0
1015  hadj.set_value(cx0 - dx)
1016  vadj.set_value(cy0 - dy)
1017  return True
1018 
1019  def _canvas_button_press(self, widget, event):
1020  if event.button == 2:
1021  self._begin_panning(widget, event)
1022  return True
1023  return False
1024 
1025  def _canvas_button_release(self, dummy_widget, event):
1026  if event.button == 2:
1027  self._end_panning(event)
1028  return True
1029  return False
1030 
1031  def _canvas_scroll_event(self, dummy_widget, event):
1032  if event.direction == Gdk.ScrollDirection.UP:
1033  self.zoom.set_value(self.zoom.get_value() * 1.25)
1034  return True
1035  elif event.direction == Gdk.ScrollDirection.DOWN:
1036  self.zoom.set_value(self.zoom.get_value() / 1.25)
1037  return True
1038  return False
1039 
1040  def get_hadjustment(self):
1041  return self._scrolled_window.get_hadjustment()
1042 
1043  def get_vadjustment(self):
1044  return self._scrolled_window.get_vadjustment()
1045 
1046  def create_gui(self):
1047  self.window = Gtk.Window()
1048  vbox = Gtk.VBox()
1049  vbox.show()
1050  self.window.add(vbox)
1051 
1052  # canvas
1053  self.canvas = GooCanvas.Canvas()
1054  self.canvas.connect_after("button-press-event", self._canvas_button_press)
1055  self.canvas.connect_after("button-release-event", self._canvas_button_release)
1056  self.canvas.connect("scroll-event", self._canvas_scroll_event)
1057  self.canvas.props.has_tooltip = True
1058  self.canvas.connect("query-tooltip", self._canvas_tooltip_cb)
1059  self.canvas.show()
1060  sw = Gtk.ScrolledWindow()
1061  sw.show()
1062  self._scrolled_window = sw
1063  sw.add(self.canvas)
1064  vbox.pack_start(sw, True, True, 4)
1065  self.canvas.set_size_request(600, 450)
1066  self.canvas.set_bounds(-10000, -10000, 10000, 10000)
1067  self.canvas.scroll_to(0, 0)
1068 
1069  self.canvas.get_root_item().add_child(self.links_group, -1)
1070  self.links_group.set_property("visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1071 
1072  self.canvas.get_root_item().add_child(self.channels_group, -1)
1073  self.channels_group.set_property("visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1074  self.channels_group.raise_(self.links_group)
1075 
1076  self.canvas.get_root_item().add_child(self.nodes_group, -1)
1077  self.nodes_group.set_property("visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1078  self.nodes_group.raise_(self.channels_group)
1079 
1080  self.hud = hud.Axes(self)
1081 
1082  hbox = Gtk.HBox()
1083  hbox.show()
1084  vbox.pack_start(hbox, False, False, 4)
1085 
1086  # zoom
1087  zoom_adj = Gtk.Adjustment(
1088  value=1.0,
1089  lower=0.01,
1090  upper=10.0,
1091  step_increment=0.02,
1092  page_increment=1.0,
1093  page_size=1.0,
1094  )
1095  self.zoom = zoom_adj
1096 
1097  def _zoom_changed(adj):
1098  self.canvas.set_scale(adj.get_value())
1099 
1100  zoom_adj.connect("value-changed", _zoom_changed)
1101  zoom = Gtk.SpinButton.new(zoom_adj, 0.1, 1)
1102  zoom.set_digits(3)
1103  zoom.show()
1104  hbox.pack_start(GObject.new(Gtk.Label, label=" Zoom:", visible=True), False, False, 4)
1105  hbox.pack_start(zoom, False, False, 4)
1106  _zoom_changed(zoom_adj)
1107 
1108  # speed
1109  speed_adj = Gtk.Adjustment(
1110  value=1.0, lower=0.01, upper=10.0, step_increment=0.02, page_increment=1.0, page_size=0
1111  )
1112 
1113  def _speed_changed(adj):
1114  self.speed = adj.get_value()
1115  self.sample_period = SAMPLE_PERIOD * adj.get_value()
1116  self._start_update_timer()
1117 
1118  speed_adj.connect("value-changed", _speed_changed)
1119  speed = Gtk.SpinButton.new(speed_adj, 1, 0)
1120  speed.set_digits(3)
1121  speed.show()
1122  hbox.pack_start(GObject.new(Gtk.Label, label=" Speed:", visible=True), False, False, 4)
1123  hbox.pack_start(speed, False, False, 4)
1124  _speed_changed(speed_adj)
1125 
1126  # Current time
1127  self.time_label = GObject.new(Gtk.Label, label=" Speed:", visible=True)
1128  self.time_label.set_width_chars(20)
1129  hbox.pack_start(self.time_label, False, False, 4)
1130 
1131  # Screenshot button
1132  screenshot_button = GObject.new(
1133  Gtk.Button,
1134  label="Snapshot",
1135  relief=Gtk.ReliefStyle.NONE,
1136  focus_on_click=False,
1137  visible=True,
1138  )
1139  hbox.pack_start(screenshot_button, False, False, 4)
1140 
1141  def load_button_icon(button, icon_name):
1142  if not Gtk.IconTheme.get_default().has_icon(icon_name):
1143  print(f"Could not load icon {icon_name}", file=sys.stderr)
1144  return
1145  image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.BUTTON)
1146  button.set_image(image)
1147  button.props.always_show_image = True
1148 
1149  load_button_icon(screenshot_button, "applets-screenshooter")
1150  screenshot_button.connect("clicked", self._take_screenshot)
1151 
1152  # Shell button
1153  if ipython_view is not None:
1154  shell_button = GObject.new(
1155  Gtk.Button,
1156  label="Shell",
1157  relief=Gtk.ReliefStyle.NONE,
1158  focus_on_click=False,
1159  visible=True,
1160  )
1161  hbox.pack_start(shell_button, False, False, 4)
1162  load_button_icon(shell_button, "gnome-terminal")
1163  shell_button.connect("clicked", self._start_shell)
1164 
1165  # Play button
1166  self.play_button = GObject.new(
1167  Gtk.ToggleButton,
1168  label="Simulate (F3)",
1169  relief=Gtk.ReliefStyle.NONE,
1170  focus_on_click=False,
1171  visible=True,
1172  )
1173  load_button_icon(self.play_button, "media-playback-start")
1174  accel_group = Gtk.AccelGroup()
1175  self.window.add_accel_group(accel_group)
1176  self.play_button.add_accelerator(
1177  "clicked", accel_group, Gdk.KEY_F3, 0, Gtk.AccelFlags.VISIBLE
1178  )
1179  self.play_button.connect("toggled", self._on_play_button_toggled)
1180  hbox.pack_start(self.play_button, False, False, 4)
1181 
1182  self.canvas.get_root_item().connect("button-press-event", self.on_root_button_press_event)
1183 
1184  vbox.pack_start(self._create_advanced_controls(), False, False, 4)
1185 
1186  display = Gdk.Display.get_default()
1187  try:
1188  monitor = display.get_primary_monitor()
1189  geometry = monitor.get_geometry()
1190  scale_factor = monitor.get_scale_factor()
1191  except AttributeError:
1192  screen = display.get_default_screen()
1193  monitor_id = screen.get_primary_monitor()
1194  geometry = screen.get_monitor_geometry(monitor_id)
1195  scale_factor = screen.get_monitor_scale_factor(monitor_id)
1196  width = scale_factor * geometry.width
1197  height = scale_factor * geometry.height
1198  self.window.set_default_size(width * 2 / 3, height * 2 / 3)
1199  self.window.show()
1200 
1201  def scan_topology(self):
1202  print("scanning topology: %i nodes..." % (ns.NodeList.GetNNodes(),))
1203  graph = pygraphviz.AGraph()
1204  seen_nodes = 0
1205  for nodeI in range(ns.NodeList.GetNNodes()):
1206  seen_nodes += 1
1207  if seen_nodes == 100:
1208  print(
1209  "scan topology... %i nodes visited (%.1f%%)"
1210  % (nodeI, 100 * nodeI / ns.NodeList.GetNNodes())
1211  )
1212  seen_nodes = 0
1213  node = ns.NodeList.GetNode(nodeI)
1214  node_name = "Node %i" % nodeI
1215  node_view = self.get_node(nodeI)
1216 
1217  mobility = ns.cppyy.gbl.hasMobilityModel(node)
1218  if mobility:
1219  node_view.set_color("red")
1220  pos = ns.cppyy.gbl.getNodePosition(node)
1221  node_view.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
1222  # print "node has mobility position -> ", "%f,%f" % (pos.x, pos.y)
1223  else:
1224  graph.add_node(node_name)
1225 
1226  for devI in range(node.GetNDevices()):
1227  device = node.GetDevice(devI)
1228  device_traits = lookup_netdevice_traits(type(device.__deref__()))
1229  if device_traits.is_wireless:
1230  continue
1231  if device_traits.is_virtual:
1232  continue
1233  channel = device.GetChannel()
1234  if channel.GetNDevices() > 2:
1235  if REPRESENT_CHANNELS_AS_NODES:
1236  # represent channels as white nodes
1237  if mobility is None:
1238  channel_name = "Channel %s" % id(channel)
1239  graph.add_edge(node_name, channel_name)
1240  self.get_channel(channel)
1241  self.create_link(self.get_node(nodeI), self.get_channel(channel))
1242  else:
1243  # don't represent channels, just add links between nodes in the same channel
1244  for otherDevI in range(channel.GetNDevices()):
1245  otherDev = channel.GetDevice(otherDevI)
1246  otherNode = otherDev.GetNode()
1247  otherNodeView = self.get_node(otherNode.GetId())
1248  if otherNode is not node:
1249  if mobility is None and not otherNodeView.has_mobility:
1250  other_node_name = "Node %i" % otherNode.GetId()
1251  graph.add_edge(node_name, other_node_name)
1252  self.create_link(self.get_node(nodeI), otherNodeView)
1253  else:
1254  for otherDevI in range(channel.GetNDevices()):
1255  otherDev = channel.GetDevice(otherDevI)
1256  otherNode = otherDev.GetNode()
1257  otherNodeView = self.get_node(otherNode.GetId())
1258  if otherNode is not node:
1259  if mobility is None and not otherNodeView.has_mobility:
1260  other_node_name = "Node %i" % otherNode.GetId()
1261  graph.add_edge(node_name, other_node_name)
1262  self.create_link(self.get_node(nodeI), otherNodeView)
1263 
1264  print("scanning topology: calling graphviz layout")
1265  graph.layout(LAYOUT_ALGORITHM)
1266  for node in graph.iternodes():
1267  # print node, "=>", node.attr['pos']
1268  node_type, node_id = node.split(" ")
1269  pos_x, pos_y = [float(s) for s in node.attr["pos"].split(",")]
1270  if node_type == "Node":
1271  obj = self.nodes[int(node_id)]
1272  elif node_type == "Channel":
1273  obj = self.channels[int(node_id)]
1274  obj.set_position(pos_x, pos_y)
1275 
1276  print("scanning topology: all done.")
1277  self.emit("topology-scanned")
1278 
1279  def get_node(self, index):
1280  try:
1281  return self.nodes[index]
1282  except KeyError:
1283  node = Node(self, index)
1284  self.nodes[index] = node
1285  self.nodes_group.add_child(node.canvas_item, -1)
1286  node.canvas_item.connect("button-press-event", self.on_node_button_press_event, node)
1287  node.canvas_item.connect(
1288  "button-release-event", self.on_node_button_release_event, node
1289  )
1290  return node
1291 
1292  def get_channel(self, ns3_channel):
1293  try:
1294  return self.channels[id(ns3_channel)]
1295  except KeyError:
1296  channel = Channel(ns3_channel)
1297  self.channels[id(ns3_channel)] = channel
1298  self.channels_group.add_child(channel.canvas_item, -1)
1299  return channel
1300 
1301  def create_link(self, node, node_or_channel):
1302  link = WiredLink(node, node_or_channel)
1303  self.links_group.add_child(link.canvas_item, -1)
1304  link.canvas_item.lower(None)
1305 
1306  def update_view(self):
1307  # print "update_view"
1308 
1309  self.time_label.set_text("Time: %f s" % ns.Simulator.Now().GetSeconds())
1310 
1311  self._update_node_positions()
1312 
1313  # Update information
1314  for info_win in self.information_windows:
1315  info_win.update()
1316 
1317  self._update_transmissions_view()
1318  self._update_drops_view()
1319 
1320  self.emit("update-view")
1321 
1322  def _update_node_positions(self):
1323  for node in self.nodes.values():
1324  if node.has_mobility:
1325  ns3_node = ns.NodeList.GetNode(node.node_index)
1326  mobility = ns.cppyy.gbl.hasMobilityModel(ns3_node)
1327  if mobility:
1328  pos = ns.cppyy.gbl.getNodePosition(ns3_node)
1329  x, y = transform_point_simulation_to_canvas(pos.x, pos.y)
1330  node.set_position(x, y)
1331  if node is self.follow_node:
1332  hadj = self._scrolled_window.get_hadjustment()
1333  vadj = self._scrolled_window.get_vadjustment()
1334  px, py = self.canvas.convert_to_pixels(x, y)
1335  hadj.set_value(px - hadj.get_page_size() / 2)
1336  vadj.set_value(py - vadj.get_page_size() / 2)
1337 
1338  def center_on_node(self, node):
1339  if isinstance(node, ns.Node):
1340  node = self.nodes[node.GetId()]
1341  elif isinstance(node, int):
1342  node = self.nodes[node]
1343  elif isinstance(node, Node):
1344  pass
1345  else:
1346  raise TypeError("expected int, viz.Node or ns.Node, not %r" % node)
1347 
1348  x, y = node.get_position()
1349  hadj = self._scrolled_window.get_hadjustment()
1350  vadj = self._scrolled_window.get_vadjustment()
1351  px, py = self.canvas.convert_to_pixels(x, y)
1352  hadj.set_value(px - hadj.get_page_size() / 2)
1353  vadj.set_value(py - vadj.get_page_size() / 2)
1354 
1355  def update_model(self):
1356  self.simulation.lock.acquire()
1357  try:
1358  self.emit("simulation-periodic-update")
1359  finally:
1360  self.simulation.lock.release()
1361 
1362  def do_simulation_periodic_update(self):
1363  smooth_factor = int(self.transmissions_smoothing_adjustment.get_value() * 10)
1364 
1365  transmissions = self.simulation.sim_helper.GetTransmissionSamples()
1366  self._last_transmissions.append(transmissions)
1367  while len(self._last_transmissions) > smooth_factor:
1368  self._last_transmissions.pop(0)
1369 
1370  drops = self.simulation.sim_helper.GetPacketDropSamples()
1371  self._last_drops.append(drops)
1372  while len(self._last_drops) > smooth_factor:
1373  self._last_drops.pop(0)
1374 
1375  def _get_label_over_line_position(self, pos1_x, pos1_y, pos2_x, pos2_y):
1376  hadj = self._scrolled_window.get_hadjustment()
1377  vadj = self._scrolled_window.get_vadjustment()
1378  bounds_x1, bounds_y1 = self.canvas.convert_from_pixels(hadj.get_value(), vadj.get_value())
1379  bounds_x2, bounds_y2 = self.canvas.convert_from_pixels(
1380  hadj.get_value() + hadj.get_page_size(), vadj.get_value() + vadj.get_page_size()
1381  )
1382  ns.PyViz.LineClipping(
1383  bounds_x1, bounds_y1, bounds_x2, bounds_y2, pos1_x, pos1_y, pos2_x, pos2_y
1384  )
1385  return (pos1_x.value + pos2_x.value) / 2, (pos1_y.value + pos2_y.value) / 2
1386 
1387  def _update_transmissions_view(self):
1388  transmissions_average = {}
1389  for transmission_set in self._last_transmissions:
1390  for transmission in transmission_set:
1391  key = (transmission.transmitter.GetId(), transmission.receiver.GetId())
1392  rx_bytes, count = transmissions_average.get(key, (0, 0))
1393  rx_bytes += transmission.bytes
1394  count += 1
1395  transmissions_average[key] = rx_bytes, count
1396 
1397  old_arrows = self._transmission_arrows
1398  for arrow, label in old_arrows:
1399  arrow.set_property("visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1400  label.set_property("visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1401  new_arrows = []
1402 
1403  k = self.node_size_adjustment.get_value() / 5
1404 
1405  for (transmitter_id, receiver_id), (rx_bytes, rx_count) in transmissions_average.items():
1406  transmitter = self.get_node(transmitter_id)
1407  receiver = self.get_node(receiver_id)
1408  try:
1409  arrow, label = old_arrows.pop()
1410  except IndexError:
1411  arrow = GooCanvas.CanvasPolyline(
1412  line_width=2.0,
1413  stroke_color_rgba=0x00C000C0,
1414  close_path=False,
1415  end_arrow=True,
1416  pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1417  )
1418  arrow.set_property("parent", self.canvas.get_root_item())
1419  arrow.raise_(None)
1420 
1421  label = GooCanvas.CanvasText(
1422  parent=self.canvas.get_root_item(),
1423  pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1424  )
1425  label.raise_(None)
1426 
1427  arrow.set_property("visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1428  line_width = max(0.1, math.log(float(rx_bytes) / rx_count / self.sample_period) * k)
1429  arrow.set_property("line-width", line_width)
1430 
1431  pos1_x, pos1_y = transmitter.get_position()
1432  pos2_x, pos2_y = receiver.get_position()
1433  points = GooCanvas.CanvasPoints.new(2)
1434  points.set_point(0, pos1_x, pos1_y)
1435  points.set_point(1, pos2_x, pos2_y)
1436  arrow.set_property("points", points)
1437 
1438  kbps = float(rx_bytes * 8) / 1e3 / rx_count / self.sample_period
1439  label.set_properties(
1440  visibility=GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD,
1441  visibility_threshold=0.5,
1442  font=("Sans Serif %f" % int(1 + BITRATE_FONT_SIZE * k)),
1443  )
1444  angle = math.atan2((pos2_y - pos1_y), (pos2_x - pos1_x))
1445  if -PI_OVER_2 <= angle <= PI_OVER_2:
1446  label.set_properties(
1447  text=("%.2f kbit/s →" % (kbps,)),
1448  alignment=Pango.Alignment.CENTER,
1449  anchor=GooCanvas.CanvasAnchorType.S,
1450  x=0,
1451  y=-line_width / 2,
1452  )
1453  else:
1454  label.set_properties(
1455  text=("← %.2f kbit/s" % (kbps,)),
1456  alignment=Pango.Alignment.CENTER,
1457  anchor=GooCanvas.CanvasAnchorType.N,
1458  x=0,
1459  y=line_width / 2,
1460  )
1461  M = cairo.Matrix()
1462  lx, ly = self._get_label_over_line_position(
1463  c_double(pos1_x), c_double(pos1_y), c_double(pos2_x), c_double(pos2_y)
1464  )
1465  M.translate(lx, ly)
1466  M.rotate(angle)
1467  try:
1468  label.set_transform(M)
1469  except KeyError:
1470  # https://gitlab.gnome.org/GNOME/pygobject/issues/16
1471  warnings.warn(
1472  "PyGobject bug causing label position error; "
1473  "should be fixed in PyGObject >= 3.29.1"
1474  )
1475  label.set_properties(x=(lx + label.props.x), y=(ly + label.props.y))
1476 
1477  new_arrows.append((arrow, label))
1478 
1479  self._transmission_arrows = new_arrows + old_arrows
1480 
1481  def _update_drops_view(self):
1482  drops_average = {}
1483  for drop_set in self._last_drops:
1484  for drop in drop_set:
1485  key = drop.transmitter.GetId()
1486  drop_bytes, count = drops_average.get(key, (0, 0))
1487  drop_bytes += drop.bytes
1488  count += 1
1489  drops_average[key] = drop_bytes, count
1490 
1491  old_arrows = self._drop_arrows
1492  for arrow, label in old_arrows:
1493  arrow.set_property("visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1494  label.set_property("visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1495  new_arrows = []
1496 
1497  # get the coordinates for the edge of screen
1498  vadjustment = self._scrolled_window.get_vadjustment()
1499  bottom_y = vadjustment.get_value() + vadjustment.get_page_size()
1500  dummy, edge_y = self.canvas.convert_from_pixels(0, bottom_y)
1501 
1502  k = self.node_size_adjustment.get_value() / 5
1503 
1504  for transmitter_id, (drop_bytes, drop_count) in drops_average.items():
1505  transmitter = self.get_node(transmitter_id)
1506  try:
1507  arrow, label = old_arrows.pop()
1508  except IndexError:
1509  arrow = GooCanvas.CanvasPolyline(
1510  line_width=2.0,
1511  stroke_color_rgba=0xC00000C0,
1512  close_path=False,
1513  end_arrow=True,
1514  pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1515  )
1516  arrow.set_property("parent", self.canvas.get_root_item())
1517  arrow.raise_(None)
1518 
1519  label = GooCanvas.CanvasText(
1520  pointer_events=GooCanvas.CanvasPointerEvents.NONE
1521  ) # , fill_color_rgba=0x00C000C0)
1522  label.set_property("parent", self.canvas.get_root_item())
1523  label.raise_(None)
1524 
1525  arrow.set_property("visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1526  arrow.set_property(
1527  "line-width",
1528  max(0.1, math.log(float(drop_bytes) / drop_count / self.sample_period) * k),
1529  )
1530  pos1_x, pos1_y = transmitter.get_position()
1531  pos2_x, pos2_y = pos1_x, edge_y
1532  points = GooCanvas.CanvasPoints.new(2)
1533  points.set_point(0, pos1_x, pos1_y)
1534  points.set_point(1, pos2_x, pos2_y)
1535  arrow.set_property("points", points)
1536 
1537  label.set_properties(
1538  visibility=GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD,
1539  visibility_threshold=0.5,
1540  font=("Sans Serif %i" % int(1 + BITRATE_FONT_SIZE * k)),
1541  text=(
1542  "%.2f kbit/s" % (float(drop_bytes * 8) / 1e3 / drop_count / self.sample_period,)
1543  ),
1544  alignment=Pango.Alignment.CENTER,
1545  x=(pos1_x + pos2_x) / 2,
1546  y=(pos1_y + pos2_y) / 2,
1547  )
1548 
1549  new_arrows.append((arrow, label))
1550 
1551  self._drop_arrows = new_arrows + old_arrows
1552 
1553  def update_view_timeout(self):
1554  # print "view: update_view_timeout called at real time ", time.time()
1555 
1556  # while the simulator is busy, run the gtk event loop
1557  while not self.simulation.lock.acquire(False):
1558  while Gtk.events_pending():
1559  Gtk.main_iteration()
1560  pause_messages = self.simulation.pause_messages
1561  self.simulation.pause_messages = []
1562  try:
1563  self.update_view()
1564  self.simulation.target_time = ns.Simulator.Now().GetSeconds() + self.sample_period
1565  # print "view: target time set to %f" % self.simulation.target_time
1566  finally:
1567  self.simulation.lock.release()
1568 
1569  if pause_messages:
1570  # print pause_messages
1571  dialog = Gtk.MessageDialog(
1572  parent=self.window,
1573  flags=0,
1574  type=Gtk.MessageType.WARNING,
1575  buttons=Gtk.ButtonsType.OK,
1576  message_format="\n".join(pause_messages),
1577  )
1578  dialog.connect("response", lambda d, r: d.destroy())
1579  dialog.show()
1580  self.play_button.set_active(False)
1581 
1582  # if we're paused, stop the update timer
1583  if not self.play_button.get_active():
1584  self._update_timeout_id = None
1585  return False
1586 
1587  # print "view: self.simulation.go.set()"
1588  self.simulation.go.set()
1589  # print "view: done."
1590  return True
1591 
1592  def _start_update_timer(self):
1593  if self._update_timeout_id is not None:
1594  GLib.source_remove(self._update_timeout_id)
1595  # print "start_update_timer"
1596  self._update_timeout_id = GLib.timeout_add(
1597  int(SAMPLE_PERIOD / min(self.speed, 1) * 1e3),
1598  self.update_view_timeout,
1599  priority=PRIORITY_UPDATE_VIEW,
1600  )
1601 
1602  def _on_play_button_toggled(self, button):
1603  if button.get_active():
1604  self._start_update_timer()
1605  else:
1606  if self._update_timeout_id is not None:
1607  GLib.source_remove(self._update_timeout_id)
1608 
1609  def _quit(self, *dummy_args):
1610  if self._update_timeout_id is not None:
1611  GLib.source_remove(self._update_timeout_id)
1612  self._update_timeout_id = None
1613  self.simulation.quit = True
1614  self.simulation.go.set()
1615  self.simulation.join()
1616  Gtk.main_quit()
1617 
1618  def _monkey_patch_ipython(self):
1619  # The user may want to access the NS 3 simulation state, but
1620  # NS 3 is not thread safe, so it could cause serious problems.
1621  # To work around this, monkey-patch IPython to automatically
1622  # acquire and release the simulation lock around each code
1623  # that is executed.
1624 
1625  original_runcode = self.ipython.runcode
1626 
1627  def runcode(ip, *args):
1628  # print "lock"
1629  self.simulation.lock.acquire()
1630  try:
1631  return original_runcode(*args)
1632  finally:
1633  # print "unlock"
1634  self.simulation.lock.release()
1635 
1636  import types
1637 
1638  self.ipython.runcode = types.MethodType(runcode, self.ipython)
1639 
1640  def autoscale_view(self):
1641  if not self.nodes:
1642  return
1643  self._update_node_positions()
1644  positions = [node.get_position() for node in self.nodes.values()]
1645  min_x, min_y = min(x for (x, y) in positions), min(y for (x, y) in positions)
1646  max_x, max_y = max(x for (x, y) in positions), max(y for (x, y) in positions)
1647  min_x_px, min_y_px = self.canvas.convert_to_pixels(min_x, min_y)
1648  max_x_px, max_y_px = self.canvas.convert_to_pixels(max_x, max_y)
1649  dx = max_x - min_x
1650  dy = max_y - min_y
1651  dx_px = max_x_px - min_x_px
1652  dy_px = max_y_px - min_y_px
1653  hadj = self._scrolled_window.get_hadjustment()
1654  vadj = self._scrolled_window.get_vadjustment()
1655  new_dx, new_dy = 1.5 * dx_px, 1.5 * dy_px
1656 
1657  if new_dx == 0 or new_dy == 0:
1658  return
1659 
1660  self.zoom.set_value(min(hadj.get_page_size() / new_dx, vadj.get_page_size() / new_dy))
1661 
1662  x1, y1 = self.canvas.convert_from_pixels(hadj.get_value(), vadj.get_value())
1663  x2, y2 = self.canvas.convert_from_pixels(
1664  (hadj.get_value() + hadj.get_page_size()), (vadj.get_value() + vadj.get_page_size())
1665  )
1666  width = x2 - x1
1667  height = y2 - y1
1668  center_x = (min_x + max_x) / 2
1669  center_y = (min_y + max_y) / 2
1670 
1671  self.canvas.scroll_to(center_x - width / 2, center_y - height / 2)
1672 
1673  return False
1674 
1675  def start(self):
1676  self.scan_topology()
1677  self.window.connect("delete-event", self._quit)
1678  # self._start_update_timer()
1679  GLib.timeout_add(200, self.autoscale_view)
1680  self.simulation.start()
1681 
1682  try:
1683  __IPYTHON__
1684  except NameError:
1685  pass
1686  else:
1687  self._monkey_patch_ipython()
1688 
1689  Gtk.main()
1690 
1691  def on_root_button_press_event(self, view, target, event):
1692  if event.button == 1:
1693  self.select_node(None)
1694  return True
1695 
1696  def on_node_button_press_event(self, view, target, event, node):
1697  button = event.button
1698  if button == 1:
1699  self.select_node(node)
1700  return True
1701  elif button == 3:
1702  self.popup_node_menu(node, event)
1703  return True
1704  elif button == 2:
1705  self.begin_node_drag(node, event)
1706  return True
1707  return False
1708 
1709  def on_node_button_release_event(self, view, target, event, node):
1710  if event.button == 2:
1711  self.end_node_drag(node)
1712  return True
1713  return False
1714 
1715  class NodeDragState(object):
1716  def __init__(self, canvas_x0, canvas_y0, sim_x0, sim_y0):
1717  self.canvas_x0 = canvas_x0
1718  self.canvas_y0 = canvas_y0
1719  self.sim_x0 = sim_x0
1720  self.sim_y0 = sim_y0
1721  self.motion_signal = None
1722 
1723  def begin_node_drag(self, node, event):
1724  self.simulation.lock.acquire()
1725  try:
1726  ns3_node = ns.NodeList.GetNode(node.node_index)
1727  mob = ns.cppyy.gbl.hasMobilityModel(ns3_node)
1728  if not mob:
1729  return
1730  if self.node_drag_state is not None:
1731  return
1732  pos = ns.cppyy.gbl.getNodePosition(ns3_node)
1733  finally:
1734  self.simulation.lock.release()
1735  devpos = self.canvas.get_window().get_device_position(event.device)
1736  x0, y0 = self.canvas.convert_from_pixels(devpos.x, devpos.y)
1737  self.node_drag_state = self.NodeDragState(x0, y0, pos.x, pos.y)
1738  self.node_drag_state.motion_signal = node.canvas_item.connect(
1739  "motion-notify-event", self.node_drag_motion, node
1740  )
1741 
1742  def node_drag_motion(self, item, targe_item, event, node):
1743  self.simulation.lock.acquire()
1744  try:
1745  ns3_node = ns.NodeList.GetNode(node.node_index)
1746  mob = ns.cppyy.gbl.hasMobilityModel(ns3_node)
1747  if not mob:
1748  return False
1749  if self.node_drag_state is None:
1750  return False
1751  devpos = self.canvas.get_window().get_device_position(event.device)
1752  canvas_x, canvas_y = self.canvas.convert_from_pixels(devpos.x, devpos.y)
1753  dx = canvas_x - self.node_drag_state.canvas_x0
1754  dy = canvas_y - self.node_drag_state.canvas_y0
1755  pos = mob.GetPosition()
1756  pos.x = self.node_drag_state.sim_x0 + transform_distance_canvas_to_simulation(dx)
1757  pos.y = self.node_drag_state.sim_y0 + transform_distance_canvas_to_simulation(dy)
1758  # print "SetPosition(%G, %G)" % (pos.x, pos.y)
1759  mob.SetPosition(pos)
1760  node.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
1761  finally:
1762  self.simulation.lock.release()
1763  return True
1764 
1765  def end_node_drag(self, node):
1766  if self.node_drag_state is None:
1767  return
1768  node.canvas_item.disconnect(self.node_drag_state.motion_signal)
1769  self.node_drag_state = None
1770 
1771  def popup_node_menu(self, node, event):
1772  menu = Gtk.Menu()
1773  self.emit("populate-node-menu", node, menu)
1774  menu.popup_at_pointer(event)
1775 
1776  def _update_ipython_selected_node(self):
1777  # If we are running under ipython -gthread, make this new
1778  # selected node available as a global 'selected_node'
1779  # variable.
1780  try:
1781  __IPYTHON__
1782  except NameError:
1783  pass
1784  else:
1785  if self.selected_node is None:
1786  ns3_node = None
1787  else:
1788  self.simulation.lock.acquire()
1789  try:
1790  ns3_node = ns.NodeList.GetNode(self.selected_node.node_index)
1791  finally:
1792  self.simulation.lock.release()
1793  self.ipython.updateNamespace({"selected_node": ns3_node})
1794 
1795  def select_node(self, node):
1796  if isinstance(node, ns.Node):
1797  node = self.nodes[node.GetId()]
1798  elif isinstance(node, int):
1799  node = self.nodes[node]
1800  elif isinstance(node, Node):
1801  pass
1802  elif node is None:
1803  pass
1804  else:
1805  raise TypeError("expected None, int, viz.Node or ns.Node, not %r" % node)
1806 
1807  if node is self.selected_node:
1808  return
1809 
1810  if self.selected_node is not None:
1811  self.selected_node.selected = False
1812  self.selected_node = node
1813  if self.selected_node is not None:
1814  self.selected_node.selected = True
1815 
1816  if self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
1817  if self.selected_node is None:
1818  self.simulation.set_nodes_of_interest([])
1819  else:
1820  self.simulation.set_nodes_of_interest([self.selected_node.node_index])
1821 
1822  self._update_ipython_selected_node()
1823 
1824  def add_information_window(self, info_win):
1825  self.information_windows.append(info_win)
1826  self.simulation.lock.acquire()
1827  try:
1828  info_win.update()
1829  finally:
1830  self.simulation.lock.release()
1831 
1832  def remove_information_window(self, info_win):
1833  self.information_windows.remove(info_win)
1834 
1835  def _canvas_tooltip_cb(self, canvas, x, y, keyboard_mode, tooltip):
1836  # print "tooltip query: ", x, y
1837  hadj = self._scrolled_window.get_hadjustment()
1838  vadj = self._scrolled_window.get_vadjustment()
1839  x, y = self.canvas.convert_from_pixels(hadj.get_value() + x, vadj.get_value() + y)
1840  item = self.canvas.get_item_at(x, y, True)
1841  # print "items at (%f, %f): %r | keyboard_mode=%r" % (x, y, item, keyboard_mode)
1842  if not item:
1843  return False
1844  while item is not None:
1845  obj = getattr(item, "pyviz_object", None)
1846  if obj is not None:
1847  obj.tooltip_query(tooltip)
1848  return True
1849  item = item.props.parent
1850  return False
1851 
1852  def _get_export_file_name(self):
1853  sel = Gtk.FileChooserNative.new(
1854  "Save...", self.canvas.get_toplevel(), Gtk.FileChooserAction.SAVE, "_Save", "_Cancel"
1855  )
1856  sel.set_local_only(True)
1857  sel.set_do_overwrite_confirmation(True)
1858  sel.set_current_name("Unnamed.pdf")
1859 
1860  filter = Gtk.FileFilter()
1861  filter.set_name("Embedded PostScript")
1862  filter.add_mime_type("image/x-eps")
1863  sel.add_filter(filter)
1864 
1865  filter = Gtk.FileFilter()
1866  filter.set_name("Portable Document Graphics")
1867  filter.add_mime_type("application/pdf")
1868  sel.add_filter(filter)
1869 
1870  filter = Gtk.FileFilter()
1871  filter.set_name("Scalable Vector Graphics")
1872  filter.add_mime_type("image/svg+xml")
1873  sel.add_filter(filter)
1874 
1875  resp = sel.run()
1876  if resp != Gtk.ResponseType.ACCEPT:
1877  sel.destroy()
1878  return None
1879 
1880  file_name = sel.get_filename()
1881  sel.destroy()
1882  return file_name
1883 
1884  def _take_screenshot(self, dummy_button):
1885  # print "Cheese!"
1886  file_name = self._get_export_file_name()
1887  if file_name is None:
1888  return
1889 
1890  # figure out the correct bounding box for what is visible on screen
1891  x1 = self._scrolled_window.get_hadjustment().get_value()
1892  y1 = self._scrolled_window.get_vadjustment().get_value()
1893  x2 = x1 + self._scrolled_window.get_hadjustment().get_page_size()
1894  y2 = y1 + self._scrolled_window.get_vadjustment().get_page_size()
1895  bounds = GooCanvas.CanvasBounds()
1896  bounds.x1, bounds.y1 = self.canvas.convert_from_pixels(x1, y1)
1897  bounds.x2, bounds.y2 = self.canvas.convert_from_pixels(x2, y2)
1898  dest_width = bounds.x2 - bounds.x1
1899  dest_height = bounds.y2 - bounds.y1
1900  # print bounds.x1, bounds.y1, " -> ", bounds.x2, bounds.y2
1901 
1902  dummy, extension = os.path.splitext(file_name)
1903  extension = extension.lower()
1904  if extension == ".eps":
1905  surface = cairo.PSSurface(file_name, dest_width, dest_height)
1906  elif extension == ".pdf":
1907  surface = cairo.PDFSurface(file_name, dest_width, dest_height)
1908  elif extension == ".svg":
1909  surface = cairo.SVGSurface(file_name, dest_width, dest_height)
1910  else:
1911  dialog = Gtk.MessageDialog(
1912  parent=self.canvas.get_toplevel(),
1913  flags=Gtk.DialogFlags.DESTROY_WITH_PARENT,
1914  type=Gtk.MessageType.ERROR,
1915  buttons=Gtk.ButtonsType.OK,
1916  message_format="Unknown extension '%s' (valid extensions are '.eps', '.svg', and '.pdf')"
1917  % (extension,),
1918  )
1919  dialog.run()
1920  dialog.destroy()
1921  return
1922 
1923  # draw the canvas to a printing context
1924  cr = cairo.Context(surface)
1925  cr.translate(-bounds.x1, -bounds.y1)
1926  self.canvas.render(cr, bounds, self.zoom.get_value())
1927  cr.show_page()
1928  surface.finish()
1929 
1930  def set_follow_node(self, node):
1931  if isinstance(node, ns.Node):
1932  node = self.nodes[node.GetId()]
1933  self.follow_node = node
1934 
1935  def _start_shell(self, dummy_button):
1936  if self.shell_window is not None:
1937  self.shell_window.present()
1938  return
1939 
1940  self.shell_window = Gtk.Window()
1941  self.shell_window.set_size_request(750, 550)
1942  self.shell_window.set_resizable(True)
1943  scrolled_window = Gtk.ScrolledWindow()
1944  scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
1945  self.ipython = ipython_view.IPythonView()
1946  self.ipython.modify_font(Pango.FontDescription(SHELL_FONT))
1947  self.ipython.set_wrap_mode(Gtk.WrapMode.CHAR)
1948  self.ipython.show()
1949  scrolled_window.add(self.ipython)
1950  scrolled_window.show()
1951  self.shell_window.add(scrolled_window)
1952  self.shell_window.show()
1953  self.shell_window.connect("destroy", self._on_shell_window_destroy)
1954 
1955  self._update_ipython_selected_node()
1956  self.ipython.updateNamespace({"viz": self})
1957 
1958  def _on_shell_window_destroy(self, window):
1959  self.shell_window = None
1960 
1961 
1962 initialization_hooks = []
1963 
1964 
1965 def add_initialization_hook(hook, *args):
1966  """
1967  Adds a callback to be called after
1968  the visualizer is initialized, like this::
1969  initialization_hook(visualizer, *args)
1970  """
1971  global initialization_hooks
1972  initialization_hooks.append((hook, args))
1973 
1974 
1975 def set_bounds(x1, y1, x2, y2):
1976  assert x2 > x1
1977  assert y2 > y1
1978 
1979  def hook(viz):
1980  cx1, cy1 = transform_point_simulation_to_canvas(x1, y1)
1981  cx2, cy2 = transform_point_simulation_to_canvas(x2, y2)
1982  viz.canvas.set_bounds(cx1, cy1, cx2, cy2)
1983 
1985 
1986 
1987 def start():
1988  assert Visualizer.INSTANCE is None
1989  if _import_error is not None:
1990  import sys
1991 
1992  print("No visualization support (%s)." % (str(_import_error),), file=sys.stderr)
1993  ns.Simulator.Run()
1994  return
1995  load_plugins()
1996  viz = Visualizer()
1997  for hook, args in initialization_hooks:
1998  GLib.idle_add(hook, viz, *args)
1999  ns.Packet.EnablePrinting()
2000  viz.start()
#define min(a, b)
Definition: 80211b.c:41
#define max(a, b)
Definition: 80211b.c:42
static bool IsFinished()
Check if the simulation should finish.
Definition: simulator.cc:171
PyVizObject class.
Definition: base.py:10
def set_position(self, x, y)
Initializer function.
Definition: core.py:590
def __init__(self, channel)
Initializer function.
Definition: core.py:570
def get_position(self)
Initializer function.
Definition: core.py:605
links
list of links
Definition: core.py:588
Node class.
Definition: core.py:95
svg_align_y
svg align Y
Definition: core.py:165
visualizer
visualier object
Definition: core.py:150
def set_label(self, label)
Set a label for the node.
Definition: core.py:217
def add_link(self, link)
Add link function.
Definition: core.py:525
def set_svg_icon(self, file_base_name, width=None, height=None, align_x=0.5, align_y=0.5)
Set a background SVG icon for the node.
Definition: core.py:171
def get_position(self)
Get position function.
Definition: core.py:484
_highlighted
is highlighted
Definition: core.py:157
def _set_selected(self, value)
Set selected function.
Definition: core.py:344
_label_canvas_item
label canvas
Definition: core.py:167
highlighted
highlighted property
Definition: core.py:386
def remove_link(self, link)
Remove link function.
Definition: core.py:536
def on_leave_notify_event(self, view, target, event)
On Leave event handle.
Definition: core.py:332
svg_item
svg item
Definition: core.py:163
def _update_svg_position(self, x, y)
Update svg position.
Definition: core.py:230
def __init__(self, visualizer, node_index)
Initialize function.
Definition: core.py:142
_has_mobility
has mobility model
Definition: core.py:155
def _get_selected(self)
Get selected function.
Definition: core.py:355
def on_enter_notify_event(self, view, target, event)
On Enter event handle.
Definition: core.py:318
def set_color(self, color)
Set color function.
Definition: core.py:506
def _get_highlighted(self)
Get highlighted function.
Definition: core.py:377
_selected
is selected
Definition: core.py:156
def _update_position(self)
Update position function.
Definition: core.py:496
def has_mobility(self)
Has mobility function.
Definition: core.py:548
svg_align_x
svg align X
Definition: core.py:164
def set_position(self, x, y)
Set position function.
Definition: core.py:444
def tooltip_query(self, tooltip)
Query tooltip.
Definition: core.py:245
def set_size(self, size)
Set size function.
Definition: core.py:388
canvas_item
canvas item
Definition: core.py:152
def _update_appearance(self)
Update the node aspect to reflect the selected/highlighted state.
Definition: core.py:399
def _set_highlighted(self, value)
Set highlighted function.
Definition: core.py:366
node_index
node index
Definition: core.py:151
ShowTransmissionsMode.
Definition: core.py:739
SimulationThread.
Definition: core.py:657
pause_messages
pause messages
Definition: core.py:688
def run(self)
Initializer function.
Definition: core.py:704
def set_nodes_of_interest(self, nodes)
Set nodes of interest function.
Definition: core.py:690
def __init__(self, viz)
Initializer function.
Definition: core.py:672
sim_helper
helper function
Definition: core.py:687
viz
Visualizer object.
Definition: core.py:681
Axes class.
Definition: hud.py:9
SvgItem class.
Definition: svgitem.py:9
string release
Definition: conf.py:54
def transform_distance_simulation_to_canvas(d)
Definition: base.py:84
def transform_distance_canvas_to_simulation(d)
Definition: base.py:92
def load_plugins()
Definition: base.py:120
def lookup_netdevice_traits(class_type)
Definition: base.py:69
def transform_point_simulation_to_canvas(x, y)
Definition: base.py:88
def add_initialization_hook(hook, *args)
Definition: core.py:1965
def set_bounds(x1, y1, x2, y2)
Definition: core.py:1975
def start()
Definition: core.py:1987
#define list