In [2]:
%matplotlib

import numpy as np
from matplotlib.patches import Circle, Wedge, Polygon
from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt
from numpy import arange
import math
import time

fig, ax = plt.subplots()

pathx = []
pathy = []
hpathx = []
hpathy = []
opathx = []
opathy = []
opath_deg = []
pilot = None
patches = []

a_path, = plt.plot(pathx, pathy)
a_hpath, = plt.plot(hpathx, hpathy, '-')
a_opath, = plt.plot(opathx, opathy, '-r*')

selected_circle = None

def add_path(x, y):
    pathx.append(x)
    pathy.append(y)

def add_helper_point(x, y):
    hpathx.append(x)
    hpathy.append(y)

def add_opti_path(x, y):
    opathx.append(x)
    opathy.append(y)


circles = []

circles.append((0,0,0.4))
circles.append((1,7,2))
circles.append((9,9,2))
circles.append((6,2,1))
circles.append((10,0,2))
circles.append((12,7,1))
circles.append((13,7,0.4))

opath_deg = [0] * len(circles)

def recalc_optimal2():
    global tdist
    global tdist2
    
    global opathx
    global opathy
    
    opathx = []
    opathy = []
    
    add_opti_path(*circles[0][:2])
    for i in range(len(opath_deg) - 2):
        i += 1
        if opath_deg[i] is not None:
            rad = math.radians(opath_deg[i])
            cx, cy, r = circles[i]
            x = cx + math.cos(rad) * r
            y = cy + math.sin(rad) * r
            add_opti_path(x, y)
    add_opti_path(*circles[-1][:2])

    tdist = 0

    for i in range(len(opathx) - 1):
        tdist += math.sqrt(pow(opathx[i] - opathx[i + 1], 2) + pow(opathy[i] - opathy[i + 1], 2))


def redraw(selected_circle = None, msg = None):
    global pathx
    global pathy
    global hpathx
    global hpathy
    global opathx
    global opathy
    global opath_deg
    global patches
    
    pathx = []
    pathy = []
    hpathx = []
    hpathy = []
    opathx = []
    opathy = []

    
    add_path(*circles[0][:2])
    for x, y, r in circles:
        add_path(x, y)

    for p in patches:
        p.remove()
    
    patches = []
    p = []
    i = 0
    for x, y, r in circles:
        if i == selected_circle:
            circle = Circle((x, y), r, alpha=0.6, ls='-', lw=1)
        else:
            circle = Circle((x, y), r, alpha=0.2, ls='-', lw=1)
            
        patches.append(ax.add_patch(circle))
        i += 1

    if pilot:
        circle = Circle(pilot, 0.05, alpha=1, ls='-', lw=1)
        patches.append(ax.add_patch(circle))

        
    x1, y1, __ = circles[-2]
    x2, y2, r = circles[-1]
      
    dx = x2 - x1
    dy = y2 - y1
    
    if dy != 0:
        k = -dx / dy
        
        q = -x2 * k + y2 

        x3 = x2 + 1
        y3 = x3 * k + q
    else:
        x3 = x2 
        y3 = y2 + 1
    
    
    dx = x3 - x2
    dy = y3 - y2
    
    d1 = math.sqrt(pow(dx, 2) + pow(dy, 2))
    m = r / d1
    
    add_helper_point(x2 + dx * m, y2 + dy * m)
    add_helper_point(x2 - dx * m, y2 - dy * m)

    
    recalc_optimal2()
    
    if not msg:
        fig.canvas.set_window_title("tot:%0.3f" % (tdist))
    else:
        fig.canvas.set_window_title(msg)
    
    
    a_path.set_data(pathx, pathy)
    a_hpath.set_data(hpathx, hpathy)
    a_opath.set_data(opathx, opathy)
    fig.canvas.draw_idle()
#    fig.canvas.draw()


def ev(event):
    x = event.xdata
    y = event.ydata
    if x is None or x is None:
        return
    
    global selected_circle
    global itter
    global pilot

    selected_circle = None
    for i in range(len(circles)):
        cx, cy, r = circles[i]
        if math.sqrt(pow(x - cx, 2) + pow(y - cy, 2)) <= r:
            selected_circle = i
    
    if event.button == 1 and selected_circle is not None:        # left mouse button
        cx, cy, r = circles[selected_circle]
        circles[selected_circle] = x, y, r
        redraw(selected_circle)
        itter = [0]

    if event.button == 3:        # right mouse button
        pilot = (x, y)
        
        cx, cy, r = circles[-1]
        dx = x - cx
        dy = y - cy
        
        d = math.sqrt(pow(dx, 2) + pow(dy, 2))
        
        m = ""
        
        m += "Circle: " + str(d < r)
        
        x1, y1, __ = circles[-2]
        x2, y2, __ = circles[-1]

        dx = x2 - x1
        dy = y2 - y1

        if dy != 0 and 0:
            k = -dx / dy
            q = -x2 * k + y2 
            res = y > k * x + q
        else:
            k = -dy / dx
            q = -y2 * k + x2 
            res = x > k * y + q

        
        
        m += " Line: " + str(res)
        
        redraw(selected_circle, m)

        
    if event.button == 2 and selected_circle is not None:        # middle mouse button
        
        start_time = time.process_time()
        while len(itter) < 5:
            improve = False
            for i in range(len(circles) - 2):
                i += 1
                min_dest = 1000000000000
                min_angle = None

                last_angle = opath_deg[i]

                global tdist2

                if len(itter) == 1:
                    start = 0
                    end = 360
                    step = 10

                if len(itter) == 2:
                    start = last_angle - 18
                    end = last_angle + 18
                    step = 1

                if len(itter) == 3:
                    start = last_angle - 0.5
                    end = last_angle + 0.5
                    step = 0.1     
                    
                if len(itter) == 4:
                    start = last_angle - 0.05
                    end = last_angle + 0.05
                    step = 0.01                

                for angle in arange(start, end, step):
                    opath_deg[i] = angle
                    recalc_optimal2()

                    a = math.sqrt(pow(opathx[i] - opathx[i - 1], 2) + pow(opathy[i] - opathy[i - 1], 2))
                    b = math.sqrt(pow(opathx[i] - opathx[i + 1], 2) + pow(opathy[i] - opathy[i + 1], 2))
                    c = a + b
                    
                    if len(itter) == 1:
                        c = a
                    
                    if min_dest > c:
                        min_dest = c
                        min_angle = angle


                if last_angle is None:
                    opath_deg[i] = min_angle  
                    improve = True
                    continue

                if abs(last_angle - min_angle) > step:
                    print(last_angle, min_angle)
                    opath_deg[i] = min_angle    
                    improve = True
                else:
                    opath_deg[i] = last_angle 

            itter[-1] += 1
            if not improve:
                itter.append(0)

        total = "%s elapsed %0.3fs" % (str(itter), time.process_time() - start_time)
        ax.set_title(total)

        redraw(None)      
            
            

       
        
def sc(event):
    global selected_circle
    
    if event.button == 'up' and selected_circle is not None:      
        if opath_deg[selected_circle] == None:
            opath_deg[selected_circle] = 0
        opath_deg[selected_circle] = (opath_deg[selected_circle] + 1 % 360)

        redraw(selected_circle)

    if event.button == 'down' and selected_circle is not None:      
        if opath_deg[selected_circle] == None:
            opath_deg[selected_circle] = 0
        opath_deg[selected_circle] = ((opath_deg[selected_circle] - 1 + 360)  % 360)

        redraw(selected_circle)

ax.set_xlim(-2, 16)
ax.set_ylim(-2, 16)

redraw(None)        


a_opath.figure.canvas.mpl_connect('motion_notify_event', ev)
a_opath.figure.canvas.mpl_connect('button_press_event', ev)
fig.canvas.mpl_connect('scroll_event', sc)

plt.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.05)
plt.axis('equal')
plt.show()


Using matplotlib backend: TkAgg
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/matplotlib/cbook/__init__.py", line 388, in process
    proxy(*args, **kwargs)
  File "/usr/lib/python3/dist-packages/matplotlib/cbook/__init__.py", line 228, in __call__
    return mtd(*args, **kwargs)
  File "<ipython-input-2-c04d853375c6>", line 226, in ev
    while len(itter) < 5:
NameError: name 'itter' is not defined
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/matplotlib/cbook/__init__.py", line 388, in process
    proxy(*args, **kwargs)
  File "/usr/lib/python3/dist-packages/matplotlib/cbook/__init__.py", line 228, in __call__
    return mtd(*args, **kwargs)
  File "<ipython-input-2-c04d853375c6>", line 226, in ev
    while len(itter) < 5:
NameError: name 'itter' is not defined
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/matplotlib/cbook/__init__.py", line 388, in process
    proxy(*args, **kwargs)
  File "/usr/lib/python3/dist-packages/matplotlib/cbook/__init__.py", line 228, in __call__
    return mtd(*args, **kwargs)
  File "<ipython-input-2-c04d853375c6>", line 226, in ev
    while len(itter) < 5:
NameError: name 'itter' is not defined
0 260
0 200
0 80
0 140
0 240
260 277
200 217
80 62
140 122
240 257
277 294
217 221
62 44
122 111
257 274
294 308
44 32
274 288
221 223
308 307.5
223 222.5
32 31.5
111 110.5
288 288.4
307.5 307.6
110.5 110.0
288.4 288.5
110.0 109.9
288.5 288.6
222.5 222.55
31.5 31.54
109.9 109.86
288.6 288.58
307.6 307.59
222.55 222.57
307.59 260
222.57 210
31.54 80
109.86 140
288.58 240
260 277
210 227
80 62
140 122
240 257
277 294
227 231
62 44
122 111
257 274
294 311
44 34
274 288
311 314
231 231.4
34 34.1
111 110.5
288 288.4
314 313.8
231.4 231.8
34.1 34.2
110.5 110.0
288.4 288.5
231.8 232.2
110.0 109.6
288.5 288.7
34.2 34.3
313.8 313.75
232.2 232.17
34.3 34.29
109.6 109.58
288.7 288.68
313.75 313.7
313.7 313.69
313.69 260
232.17 210
34.29 80
109.58 140
288.68 240
260 277
210 227
80 62
140 122
240 235
277 294
227 231
62 44
122 112
235 240
294 311
44 34
311 314
231 231.4
112 112.4
240 239.7
314 313.8
231.4 231.8
34 33.8
112.4 112.8
239.7 239.5
231.8 232.2
313.8 313.75
33.8 33.75
239.5 239.47
313.75 313.7
33.75 33.7
112.8 112.81
239.47 239.46
313.7 313.68
33.7 33.65
313.68 250
232.2 220
33.65 80
112.81 140
250 267
220 235
80 62
140 122
239.46 234.46
267 284
235 237
62 44
122 112
234.46 240.46
284 301
237 239
44 33
301 314
314 314.1
239 239.4
33 32.7
112 112.4
240.46 239.96
239.4 239.8
112.4 112.8
239.96 239.46
32.7 32.5
314.1 314.05
32.5 32.48
112.8 112.84
239.46 239.44
314.05 314.0
32.48 32.47
112.84 112.88
239.44 239.42
314.0 313.95
32.47 32.46
112.88 112.92
239.42 239.39
112.92 112.93
32.46 32.45
313.95 210
32.45 70
112.93 140
210 192
70 52
140 122
239.39 234.39
192 178
52 34
122 113
234.39 239.39
239.8 241.8
34 32
178 178.4
32 32.1
178.4 178.8
178.8 179.2
179.2 179.16
241.8 241.85
113 112.97
239.39 239.36
179.16 179.19
241.85 241.9
179.19 179.22
241.9 241.92
179.22 230
241.92 230
32.1 80
112.97 140
230 225
230 238
80 62
140 122
239.36 234.36
225 228
238 240
62 44
122 112
234.36 240.36
44 33
240 242
228 228.4
242 241.8
33 32.5
112 112.4
240.36 239.86
228.4 228.8
32.5 32.2
112.4 112.8
239.86 239.46
228.8 229.0
112.8 113.0
239.46 239.36
229.0 228.96
241.8 241.85
32.2 32.15
113.0 112.96
228.96 228.98
241.85 241.9
32.15 32.1
112.96 112.97
228.98 229.0
241.9 241.93
229.0 240
241.93 220
32.1 80
112.97 140
240 256
220 237
80 62
140 122
239.36 234.36
256 266
237 240
62 44
122 112
234.36 240.36
266 268
44 33
240 242
268 268.4
242 241.8
33 32.5
112 112.4
240.36 239.86
268.4 268.8
32.5 32.2
112.4 112.8
239.86 239.46
268.8 269.0
112.8 113.0
239.46 239.36
269.0 269.05
241.8 241.85
32.2 32.15
113.0 112.96
269.05 269.08
241.85 241.9
32.15 32.1
112.96 112.97
269.08 269.11
241.9 241.92
269.11 220
241.92 230
32.1 80
112.97 140
220 225
230 238
80 62
140 122
239.36 234.36
238 240
62 44
122 112
234.36 240.36
44 33
240 242
242 241.9
33 32.5
112 112.4
240.36 239.86
32.5 32.2
112.4 112.8
239.86 239.46
112.8 113.0
239.46 239.36
225 224.99
241.9 241.92
32.2 32.15
113.0 112.96
32.15 32.1
112.96 112.97
224.99 240
241.92 220
32.1 80
112.97 140
240 257
220 237
80 62
140 122
239.36 234.36
257 270
237 240
62 44
122 112
234.36 240.36
44 33
240 242
270 272
272 272.3
242 241.9
33 32.5
112 112.4
240.36 239.86
32.5 32.2
112.4 112.8
239.86 239.46
112.8 113.0
239.46 239.36
272.3 272.25
241.9 241.92
32.2 32.15
113.0 112.96
32.15 32.1
112.96 112.97

In [ ]:


In [ ]: