| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 | # This code is part of Grandalf#  Copyright (C) 2011 Axel Tillequin (bdcht3@gmail.com) and others# published under GPLv2 license or EPLv1 license# Contributor(s): Axel Tillequin, Fabio Zadrozny#  Edge routing algorithms.#  These are mosty helpers for routing an edge 'e' through#  points pts with various tweaks like moving the starting point#  to the intersection with the bounding box and taking some constraints#  into account, and/or moving the head also to its prefered position.#  Of course, since gandalf only works with bounding boxes, the exact#  shape of the nodes are not known and the edge drawing inside the bb#  shall be performed by the drawing engine associated with 'views'.#  (e.g. look at intersectC when the node shape is a circle)from mantis.grandalf.utils.geometry import intersectR, getangle, sqrt# ------------------------------------------------------------------------------class EdgeViewer(object):    def setpath(self, pts):        self._pts = pts# ------------------------------------------------------------------------------#  basic edge routing with lines : nothing to do for routing#  since the layout engine has already provided to list of points through which#  the edge shall be drawn. We just compute the position where to adjust the#  tail and head.def route_with_lines(e, pts):    assert hasattr(e, "view")    tail_pos = intersectR(e.v[0].view, topt=pts[1])    head_pos = intersectR(e.v[1].view, topt=pts[-2])    pts[0] = tail_pos    pts[-1] = head_pos    e.view.head_angle = getangle(pts[-2], pts[-1])# ------------------------------------------------------------------------------#  enhanced edge routing where 'corners' of the above polyline route are#  rounded with a bezier curve.def route_with_splines(e, pts):    from mantis.grandalf.utils.geometry import setroundcorner    route_with_lines(e, pts)    splines = setroundcorner(e, pts)    e.view.splines = splinesdef _gen_point(p1, p2, new_distance):    from mantis.grandalf.utils.geometry import new_point_at_distance    initial_distance = distance = sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)    if initial_distance < 1e-10:        return None    if distance > new_distance:        distance = distance - new_distance    else:        return None    angle = getangle(p1, p2)    new = new_point_at_distance(p1, distance, angle)    return newdef _gen_smoother_middle_points_from_3_points(pts, initial):    p1 = pts[0]    p2 = pts[1]    p3 = pts[2]    distance1 = sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)    distance2 = sqrt((p3[0] - p1[0]) ** 2 + (p3[1] - p1[1]) ** 2)    if distance1 < 1e-10 or distance2 < 1e-10:        yield p2    else:        if distance1 < initial or distance2 < initial:            yield p2        else:            p2a = _gen_point(p1, p2, initial)            p2b = _gen_point(p3, p2, initial)            if p2a is None or p2b is None:                yield p2            else:                yield p2a                yield p2b# Future work: possibly work better when we already have 4 points?# maybe: http://stackoverflow.com/questions/1251438/catmull-rom-splines-in-pythondef _round_corners(pts, round_at_distance):    if len(pts) > 2:        calc_with_distance = round_at_distance        while calc_with_distance > 0.5:            new_lst = [pts[0]]            for i, curr in enumerate(pts[1:-1]):                i += 1                p1 = pts[i - 1]                p2 = curr                p3 = pts[i + 1]                if len(pts) > 3:                    # i.e.: at least 4 points                    if sqrt((p3[0] - p2[0]) ** 2 + (p3[1] - p2[1]) ** 2) < (                        2 * calc_with_distance                    ):                        # prevent from crossing over.                        new_lst.append(p2)                        continue                generated = _gen_smoother_middle_points_from_3_points(                    [p1, p2, p3], calc_with_distance                )                for j in generated:                    new_lst.append(j)            new_lst.append(pts[-1])            pts = new_lst            calc_with_distance /= 2.0    return pts# ------------------------------------------------------------------------------# Routing with a custom algorithm to round corners# It works by generating new points up to a distance from where an edge is# found (and then iteratively refining based on that).# This is a custom implementation as this interpolation method worked# well for me where others weren't so great.# This is the point where it'll start rounding from an edge.# (can be changed to decide up to which distance it starts# rounding from an edge).ROUND_AT_DISTANCE = 40def route_with_rounded_corners(e, pts):    route_with_lines(e, pts)    new_pts = _round_corners(pts, round_at_distance=ROUND_AT_DISTANCE)    pts[:] = new_pts[:]
 |