routing.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. # This code is part of Grandalf
  2. # Copyright (C) 2011 Axel Tillequin (bdcht3@gmail.com) and others
  3. # published under GPLv2 license or EPLv1 license
  4. # Contributor(s): Axel Tillequin, Fabio Zadrozny
  5. # Edge routing algorithms.
  6. # These are mosty helpers for routing an edge 'e' through
  7. # points pts with various tweaks like moving the starting point
  8. # to the intersection with the bounding box and taking some constraints
  9. # into account, and/or moving the head also to its prefered position.
  10. # Of course, since gandalf only works with bounding boxes, the exact
  11. # shape of the nodes are not known and the edge drawing inside the bb
  12. # shall be performed by the drawing engine associated with 'views'.
  13. # (e.g. look at intersectC when the node shape is a circle)
  14. from mantis.grandalf.utils.geometry import intersectR, getangle, sqrt
  15. # ------------------------------------------------------------------------------
  16. class EdgeViewer(object):
  17. def setpath(self, pts):
  18. self._pts = pts
  19. # ------------------------------------------------------------------------------
  20. # basic edge routing with lines : nothing to do for routing
  21. # since the layout engine has already provided to list of points through which
  22. # the edge shall be drawn. We just compute the position where to adjust the
  23. # tail and head.
  24. def route_with_lines(e, pts):
  25. assert hasattr(e, "view")
  26. tail_pos = intersectR(e.v[0].view, topt=pts[1])
  27. head_pos = intersectR(e.v[1].view, topt=pts[-2])
  28. pts[0] = tail_pos
  29. pts[-1] = head_pos
  30. e.view.head_angle = getangle(pts[-2], pts[-1])
  31. # ------------------------------------------------------------------------------
  32. # enhanced edge routing where 'corners' of the above polyline route are
  33. # rounded with a bezier curve.
  34. def route_with_splines(e, pts):
  35. from mantis.grandalf.utils.geometry import setroundcorner
  36. route_with_lines(e, pts)
  37. splines = setroundcorner(e, pts)
  38. e.view.splines = splines
  39. def _gen_point(p1, p2, new_distance):
  40. from mantis.grandalf.utils.geometry import new_point_at_distance
  41. initial_distance = distance = sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)
  42. if initial_distance < 1e-10:
  43. return None
  44. if distance > new_distance:
  45. distance = distance - new_distance
  46. else:
  47. return None
  48. angle = getangle(p1, p2)
  49. new = new_point_at_distance(p1, distance, angle)
  50. return new
  51. def _gen_smoother_middle_points_from_3_points(pts, initial):
  52. p1 = pts[0]
  53. p2 = pts[1]
  54. p3 = pts[2]
  55. distance1 = sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)
  56. distance2 = sqrt((p3[0] - p1[0]) ** 2 + (p3[1] - p1[1]) ** 2)
  57. if distance1 < 1e-10 or distance2 < 1e-10:
  58. yield p2
  59. else:
  60. if distance1 < initial or distance2 < initial:
  61. yield p2
  62. else:
  63. p2a = _gen_point(p1, p2, initial)
  64. p2b = _gen_point(p3, p2, initial)
  65. if p2a is None or p2b is None:
  66. yield p2
  67. else:
  68. yield p2a
  69. yield p2b
  70. # Future work: possibly work better when we already have 4 points?
  71. # maybe: http://stackoverflow.com/questions/1251438/catmull-rom-splines-in-python
  72. def _round_corners(pts, round_at_distance):
  73. if len(pts) > 2:
  74. calc_with_distance = round_at_distance
  75. while calc_with_distance > 0.5:
  76. new_lst = [pts[0]]
  77. for i, curr in enumerate(pts[1:-1]):
  78. i += 1
  79. p1 = pts[i - 1]
  80. p2 = curr
  81. p3 = pts[i + 1]
  82. if len(pts) > 3:
  83. # i.e.: at least 4 points
  84. if sqrt((p3[0] - p2[0]) ** 2 + (p3[1] - p2[1]) ** 2) < (
  85. 2 * calc_with_distance
  86. ):
  87. # prevent from crossing over.
  88. new_lst.append(p2)
  89. continue
  90. generated = _gen_smoother_middle_points_from_3_points(
  91. [p1, p2, p3], calc_with_distance
  92. )
  93. for j in generated:
  94. new_lst.append(j)
  95. new_lst.append(pts[-1])
  96. pts = new_lst
  97. calc_with_distance /= 2.0
  98. return pts
  99. # ------------------------------------------------------------------------------
  100. # Routing with a custom algorithm to round corners
  101. # It works by generating new points up to a distance from where an edge is
  102. # found (and then iteratively refining based on that).
  103. # This is a custom implementation as this interpolation method worked
  104. # well for me where others weren't so great.
  105. # This is the point where it'll start rounding from an edge.
  106. # (can be changed to decide up to which distance it starts
  107. # rounding from an edge).
  108. ROUND_AT_DISTANCE = 40
  109. def route_with_rounded_corners(e, pts):
  110. route_with_lines(e, pts)
  111. new_pts = _round_corners(pts, round_at_distance=ROUND_AT_DISTANCE)
  112. pts[:] = new_pts[:]