1
- # coding=utf8
2
1
# BézierBuilder
3
2
#
4
3
# Copyright (c) 2013, Juan Luis Cano Rodríguez <juanlu001@gmail.com>
32
31
$ python bezier_builder.py
33
32
34
33
"""
35
- from __future__ import division , print_function , absolute_import
36
34
37
- import numpy as np
38
35
from math import factorial
39
- from scipy import signal
40
36
37
+ import numpy as np
38
+ from matplotlib .backends .qt_compat import QtCore
41
39
from matplotlib .lines import Line2D
42
- from matplotlib . backends . qt_compat import QtGui , QtCore
40
+
43
41
from .minimvc import Trigger
44
42
45
- class ControlPointModel (object ):
43
+
44
+ class ControlPointModel :
46
45
def __init__ (self , xp , yp , fixed = None ):
47
46
# fixed is either None (if no point is fixed) or and index of a fixed
48
47
# point
@@ -84,22 +83,22 @@ def set_control_points(self, xp, yp, fixed=None):
84
83
self .trigger .fire ()
85
84
86
85
87
- class ControlPointBuilder ( object ) :
86
+ class ControlPointBuilder :
88
87
def __init__ (self , ax , control_point_model ):
89
88
self .ax = ax
90
89
self .control_point_model = control_point_model
91
90
92
91
self .canvas = self .ax .figure .canvas
93
92
xp , yp , _ = self .control_point_model .get_control_points ()
94
- self .control_polygon = Line2D (xp , yp ,
95
- ls = "--" , c = "#666666" , marker = "x" ,
96
- mew = 2 , mec = "#204a87" )
93
+ self .control_polygon = Line2D (
94
+ xp , yp , ls = "--" , c = "#666666" , marker = "x" , mew = 2 , mec = "#204a87"
95
+ )
97
96
self .ax .add_line (self .control_polygon )
98
97
99
98
# Event handler for mouse clicking
100
- self .canvas .mpl_connect (' button_press_event' , self .on_button_press )
101
- self .canvas .mpl_connect (' button_release_event' , self .on_button_release )
102
- self .canvas .mpl_connect (' motion_notify_event' , self .on_motion_notify )
99
+ self .canvas .mpl_connect (" button_press_event" , self .on_button_press )
100
+ self .canvas .mpl_connect (" button_release_event" , self .on_button_release )
101
+ self .canvas .mpl_connect (" motion_notify_event" , self .on_motion_notify )
103
102
104
103
self ._index = None # Active vertex
105
104
@@ -109,7 +108,7 @@ def __init__(self, ax, control_point_model):
109
108
110
109
def on_button_press (self , event ):
111
110
modkey = event .guiEvent .modifiers ()
112
- # Ignore clicks outside axes
111
+ # Ignore clicks outside axes
113
112
if event .inaxes != self .ax :
114
113
return
115
114
res , ind = self .control_polygon .contains (event )
@@ -118,8 +117,7 @@ def on_button_press(self, event):
118
117
if res and (modkey == QtCore .Qt .ControlModifier or self .mode == "remove" ):
119
118
# Control-click deletes
120
119
self .control_point_model .remove_point (ind ["ind" ][0 ])
121
- if (modkey == QtCore .Qt .ShiftModifier or self .mode == "add" ):
122
-
120
+ if modkey == QtCore .Qt .ShiftModifier or self .mode == "add" :
123
121
# Adding a new point. Find the two closest points and insert it in
124
122
# between them.
125
123
total_squared_dists = []
@@ -132,10 +130,7 @@ def on_button_press(self, event):
132
130
total_squared_dists .append (dist )
133
131
best = np .argmin (total_squared_dists )
134
132
135
- self .control_point_model .add_point (best + 1 ,
136
- event .xdata ,
137
- event .ydata )
138
-
133
+ self .control_point_model .add_point (best + 1 , event .xdata , event .ydata )
139
134
140
135
def on_button_release (self , event ):
141
136
if event .button != 1 :
@@ -170,7 +165,7 @@ def compute_bezier_points(xp, yp, at, method, grid=256):
170
165
# arclength(t), and then invert it.
171
166
t = np .linspace (0 , 1 , grid )
172
167
173
- arclength = compute_arc_length (xp , yp , method , t = t )
168
+ arclength = compute_arc_length (xp , yp , method , t = t )
174
169
arclength /= arclength [- 1 ]
175
170
# Now (t, arclength) is a lookup table describing the t -> arclength
176
171
# mapping. Invert it to get at -> t
@@ -181,6 +176,7 @@ def compute_bezier_points(xp, yp, at, method, grid=256):
181
176
182
177
return method (list (zip (xp , yp )), at_t ).T
183
178
179
+
184
180
def compute_arc_length (xp , yp , method , t = None , grid = 256 ):
185
181
if t is None :
186
182
t = np .linspace (0 , 1 , grid )
@@ -194,7 +190,8 @@ def compute_arc_length(xp, yp, method, t=None, grid=256):
194
190
np .hypot (x_deltas , y_deltas , out = arclength_deltas [1 :])
195
191
return np .cumsum (arclength_deltas )
196
192
197
- class SingleBezierCurveModel (object ):
193
+
194
+ class SingleBezierCurveModel :
198
195
def __init__ (self , control_point_model , method = "CatmulClark" ):
199
196
self .method = eval (method )
200
197
self .control_point_model = control_point_model
@@ -216,15 +213,14 @@ def _refresh(self):
216
213
# self.canvas.draw()
217
214
218
215
219
- class TwoBezierCurveModel ( object ) :
216
+ class TwoBezierCurveModel :
220
217
def __init__ (self , control_point_model , method = "CatmulClark" ):
221
218
self .method = eval (method )
222
219
self .control_point_model = control_point_model
223
220
x , y = self .get_bezier_points ()
224
221
self .bezier_curve = Line2D (x , y )
225
222
self .trigger = self .control_point_model .trigger
226
223
self .trigger .add_callback (self ._refresh )
227
-
228
224
229
225
def get_bezier_points (self , num = 200 ):
230
226
return self .get_bezier_points_at (np .linspace (0 , 1 , num ))
@@ -233,15 +229,15 @@ def get_bezier_points_at(self, at, grid=256):
233
229
at = np .asarray (at )
234
230
if at .ndim == 0 :
235
231
at = np .array ([at ])
236
-
237
- low_mask = ( at < 0.5 )
238
- high_mask = ( at >= 0.5 )
232
+
233
+ low_mask = at < 0.5
234
+ high_mask = at >= 0.5
239
235
240
236
xp , yp , fixed = self .control_point_model .get_control_points ()
241
237
assert fixed is not None
242
238
243
- low_xp = xp [:fixed + 1 ]
244
- low_yp = yp [:fixed + 1 ]
239
+ low_xp = xp [: fixed + 1 ]
240
+ low_yp = yp [: fixed + 1 ]
245
241
high_xp = xp [fixed :]
246
242
high_yp = yp [fixed :]
247
243
@@ -257,21 +253,23 @@ def get_bezier_points_at(self, at, grid=256):
257
253
low_at = (0.5 - (0.5 - low_at ) * sf ) * 2
258
254
else :
259
255
high_at = (0.5 + (high_at - 0.5 ) * sf ) * 2 - 1
260
- low_at = low_at * 2
261
-
262
- low_points = compute_bezier_points (low_xp , low_yp ,
263
- low_at , self .method , grid = grid )
264
- high_points = compute_bezier_points (high_xp , high_yp ,
265
- high_at , self .method , grid = grid )
266
- out = np .concatenate ([low_points ,high_points ], 1 )
256
+ low_at = low_at * 2
257
+
258
+ low_points = compute_bezier_points (
259
+ low_xp , low_yp , low_at , self .method , grid = grid
260
+ )
261
+ high_points = compute_bezier_points (
262
+ high_xp , high_yp , high_at , self .method , grid = grid
263
+ )
264
+ out = np .concatenate ([low_points , high_points ], 1 )
267
265
return out
268
266
269
267
def _refresh (self ):
270
268
x , y = self .get_bezier_points ()
271
269
self .bezier_curve .set_data (x , y )
272
270
273
271
274
- class BezierCurveView ( object ) :
272
+ class BezierCurveView :
275
273
def __init__ (self , ax , bezier_curve_model ):
276
274
self .ax = ax
277
275
self .bezier_curve_model = bezier_curve_model
@@ -291,19 +289,18 @@ def _refresh(self):
291
289
292
290
293
291
# We used to use scipy.special.binom here,
294
- # but reimplementing it ourself lets us avoid pulling in a dependency
292
+ # but reimplementing it ourself lets us avoid pulling in a dependency
295
293
# scipy just for that one function.
296
294
def binom (n , k ):
297
295
return factorial (n ) * 1.0 / (factorial (k ) * factorial (n - k ))
298
296
299
- def Bernstein (n , k ):
300
- """Bernstein polynomial.
301
297
302
- """
298
+ def Bernstein (n , k ):
299
+ """Bernstein polynomial."""
303
300
coeff = binom (n , k )
304
301
305
302
def _bpoly (x ):
306
- return coeff * x ** k * (1 - x ) ** (n - k )
303
+ return coeff * x ** k * (1 - x ) ** (n - k )
307
304
308
305
return _bpoly
309
306
@@ -318,7 +315,8 @@ def Bezier(points, at):
318
315
curve = np .zeros ((at_flat .shape [0 ], 2 ))
319
316
for ii in range (N ):
320
317
curve += np .outer (Bernstein (N - 1 , ii )(at_flat ), points [ii ])
321
- return curve .reshape (at .shape + (2 ,))
318
+ return curve .reshape ((* at .shape , 2 ))
319
+
322
320
323
321
def CatmulClark (points , at ):
324
322
points = np .asarray (points )
@@ -327,19 +325,10 @@ def CatmulClark(points, at):
327
325
new_p = np .zeros ((2 * len (points ), 2 ))
328
326
new_p [0 ] = points [0 ]
329
327
new_p [- 1 ] = points [- 1 ]
330
- new_p [1 :- 2 :2 ] = 3 / 4. * points [:- 1 ] + 1 / 4. * points [1 :]
331
- new_p [2 :- 1 :2 ] = 1 / 4. * points [:- 1 ] + 3 / 4. * points [1 :]
328
+ new_p [1 :- 2 :2 ] = 3 / 4.0 * points [:- 1 ] + 1 / 4.0 * points [1 :]
329
+ new_p [2 :- 1 :2 ] = 1 / 4.0 * points [:- 1 ] + 3 / 4.0 * points [1 :]
332
330
points = new_p
333
331
xp , yp = zip (* points )
334
332
xp = np .interp (at , np .linspace (0 , 1 , len (xp )), xp )
335
333
yp = np .interp (at , np .linspace (0 , 1 , len (yp )), yp )
336
334
return np .asarray (list (zip (xp , yp )))
337
-
338
-
339
-
340
-
341
-
342
-
343
-
344
-
345
-
0 commit comments