1
1
"""
2
- A module providing some utility functions regarding bezier path manipulation.
2
+ A module providing some utility functions regarding Bezier path manipulation.
3
3
"""
4
4
5
5
import numpy as np
@@ -18,7 +18,7 @@ def get_intersection(cx1, cy1, cos_t1, sin_t1,
18
18
cx2 , cy2 , cos_t2 , sin_t2 ):
19
19
"""
20
20
Return the intersection between the line through (*cx1*, *cy1*) at angle
21
- *t1* and the line through (*cx2, cy2) at angle *t2*.
21
+ *t1* and the line through (*cx2*, * cy2* ) at angle *t2*.
22
22
"""
23
23
24
24
# line1 => sin_t1 * (x - cx1) - cos_t1 * (y - cy1) = 0.
@@ -49,7 +49,7 @@ def get_intersection(cx1, cy1, cos_t1, sin_t1,
49
49
50
50
def get_normal_points (cx , cy , cos_t , sin_t , length ):
51
51
"""
52
- For a line passing through (*cx*, *cy*) and having a angle *t*, return
52
+ For a line passing through (*cx*, *cy*) and having an angle *t*, return
53
53
locations of the two points located along its perpendicular line at the
54
54
distance of *length*.
55
55
"""
@@ -79,7 +79,7 @@ def _de_casteljau1(beta, t):
79
79
80
80
def split_de_casteljau (beta , t ):
81
81
"""
82
- Split a bezier segment defined by its control points *beta* into two
82
+ Split a Bezier segment defined by its control points *beta* into two
83
83
separate segments divided at *t* and return their control points.
84
84
"""
85
85
beta = np .asarray (beta )
@@ -99,21 +99,41 @@ def split_de_casteljau(beta, t):
99
99
def find_bezier_t_intersecting_with_closedpath (
100
100
bezier_point_at_t , inside_closedpath , t0 = 0. , t1 = 1. , tolerance = 0.01 ):
101
101
"""
102
- Find a parameter t0 and t1 of the given bezier path which
103
- bounds the intersecting points with a provided closed
104
- path(*inside_closedpath*). Search starts from *t0* and *t1* and it
105
- uses a simple bisecting algorithm therefore one of the end point
106
- must be inside the path while the other doesn't. The search stop
107
- when |t0-t1| gets smaller than the given tolerance.
108
- value for
102
+ Find the intersection of the Bezier curve with a closed path.
109
103
110
- - bezier_point_at_t : a function which returns x, y coordinates at *t*
104
+ The intersection point *t* is approximated by two parameters *t0*, *t1*
105
+ such that *t0* <= *t* <= *t1*.
111
106
112
- - inside_closedpath : return True if the point is inside the path
107
+ Search starts from *t0* and *t1* and uses a simple bisecting algorithm
108
+ therefore one of the end points must be inside the path while the other
109
+ doesn't. The search stops when the distance of the points parametrized by
110
+ *t0* and *t1* gets smaller than the given *tolerance*.
113
111
114
- """
115
- # inside_closedpath : function
112
+ Parameters
113
+ ----------
114
+ bezier_point_at_t : callable
115
+ A function returning x, y coordinates of the Bezier at parameter *t*.
116
+ It must have the signature::
117
+
118
+ bezier_point_at_t(t: float) -> Tuple[float, float]
119
+
120
+ inside_closedpath : callable
121
+ A function returning True if a given point (x, y) is inside the
122
+ closed path. It must have the signature::
123
+
124
+ inside_closedpath(point: Tuple[float, float]) -> bool
116
125
126
+ t0, t1 : float
127
+ Start parameters for the search.
128
+
129
+ tolerance : float
130
+ Maximal allowed distance between the final points.
131
+
132
+ Returns
133
+ -------
134
+ t0, t1 : float
135
+ The Bezier path parameters.
136
+ """
117
137
start = bezier_point_at_t (t0 )
118
138
end = bezier_point_at_t (t1 )
119
139
@@ -147,21 +167,22 @@ def find_bezier_t_intersecting_with_closedpath(
147
167
148
168
class BezierSegment :
149
169
"""
150
- A simple class of a 2-dimensional bezier segment
170
+ A 2-dimensional Bezier segment.
171
+
172
+ Parameters
173
+ ----------
174
+ control_points : array-like (N, 2)
175
+ A list of the (x, y) positions of control points of the Bezier line.
176
+ This must contain N points, where N is the order of the Bezier line.
177
+ 1 <= N <= 3 is supported.
151
178
"""
152
-
153
- # Higher order bezier lines can be supported by simplying adding
179
+ # Higher order Bezier lines can be supported by simplying adding
154
180
# corresponding values.
155
181
_binom_coeff = {1 : np .array ([1. , 1. ]),
156
182
2 : np .array ([1. , 2. , 1. ]),
157
183
3 : np .array ([1. , 3. , 3. , 1. ])}
158
184
159
185
def __init__ (self , control_points ):
160
- """
161
- *control_points* : location of contol points. It needs have a
162
- shape of n * 2, where n is the order of the bezier line. 1<=
163
- n <= 3 is supported.
164
- """
165
186
_o = len (control_points )
166
187
self ._orders = np .arange (_o )
167
188
@@ -171,7 +192,7 @@ def __init__(self, control_points):
171
192
self ._py = yy * _coeff
172
193
173
194
def point_at_t (self , t ):
174
- "evaluate a point at t "
195
+ """Return the point (x, y) at parameter *t*."" "
175
196
tt = ((1 - t ) ** self ._orders )[::- 1 ] * t ** self ._orders
176
197
_x = np .dot (tt , self ._px )
177
198
_y = np .dot (tt , self ._py )
@@ -182,9 +203,23 @@ def point_at_t(self, t):
182
203
def split_bezier_intersecting_with_closedpath (
183
204
bezier , inside_closedpath , tolerance = 0.01 ):
184
205
"""
185
- bezier : control points of the bezier segment
186
- inside_closedpath : a function which returns true if the point is inside
187
- the path
206
+ Split a Bezier curve into two at the intersection with a closed path.
207
+
208
+ Parameters
209
+ ----------
210
+ bezier : array-like(N, 2)
211
+ Control points of the Bezier segment. See `.BezierSegment`.
212
+ inside_closedpath : callable
213
+ A function returning True if a given point (x, y) is inside the
214
+ closed path. See also `.find_bezier_t_intersecting_with_closedpath`.
215
+ tolerance : float
216
+ The tolerance for the intersection. See also
217
+ `.find_bezier_t_intersecting_with_closedpath`.
218
+
219
+ Returns
220
+ -------
221
+ left, right
222
+ Lists of control points for the two Bezier segments.
188
223
"""
189
224
190
225
bz = BezierSegment (bezier )
@@ -205,12 +240,18 @@ def find_r_to_boundary_of_closedpath(
205
240
Find a radius r (centered at *xy*) between *rmin* and *rmax* at
206
241
which it intersect with the path.
207
242
208
- inside_closedpath : function
209
- cx, cy : center
210
- cos_t, sin_t : cosine and sine for the angle
211
- rmin, rmax :
243
+ Parameters
244
+ ----------
245
+ inside_closedpath : callable
246
+ A function returning True if a given point (x, y) is inside the
247
+ closed path.
248
+ xy : float, float
249
+ The center of the radius.
250
+ cos_t, sin_t : float
251
+ Cosine and sine for the angle.
252
+ rmin, rmax : float
253
+ Starting parameters for the radius search.
212
254
"""
213
-
214
255
cx , cy = xy
215
256
216
257
def _f (r ):
@@ -228,7 +269,6 @@ def split_path_inout(path, inside, tolerance=0.01, reorder_inout=False):
228
269
Divide a path into two segments at the point where ``inside(x, y)`` becomes
229
270
False.
230
271
"""
231
-
232
272
path_iter = path .iter_segments ()
233
273
234
274
ctl_points , command = next (path_iter )
@@ -287,6 +327,14 @@ def split_path_inout(path, inside, tolerance=0.01, reorder_inout=False):
287
327
288
328
289
329
def inside_circle (cx , cy , r ):
330
+ """
331
+ Return a function that checks whether a point is in a circle with center
332
+ (*cx*, *cy*) and radius *r*.
333
+
334
+ The returned function has the signature::
335
+
336
+ f(xy: Tuple[float, float]) -> bool
337
+ """
290
338
r2 = r ** 2
291
339
292
340
def _f (xy ):
@@ -295,7 +343,7 @@ def _f(xy):
295
343
return _f
296
344
297
345
298
- # quadratic bezier lines
346
+ # quadratic Bezier lines
299
347
300
348
def get_cos_sin (x0 , y0 , x1 , y1 ):
301
349
dx , dy = x1 - x0 , y1 - y0
@@ -309,8 +357,22 @@ def get_cos_sin(x0, y0, x1, y1):
309
357
@cbook ._rename_parameter ("3.1" , "tolerence" , "tolerance" )
310
358
def check_if_parallel (dx1 , dy1 , dx2 , dy2 , tolerance = 1.e-5 ):
311
359
"""
312
- Return 1 if two lines are parallel in same direction, -1 if two lines are
313
- parallel in opposite direction, 0 otherwise.
360
+ Check if two lines are parallel.
361
+
362
+ Parameters
363
+ ----------
364
+ dx1, dy1, dx2, dy2 : float
365
+ The gradients *dy*/*dx* of the two lines.
366
+ tolerance : float
367
+ The angular tolerance in radians up to which the lines are considered
368
+ parallel.
369
+
370
+ Returns
371
+ -------
372
+ is_parallel
373
+ - 1 if two lines are parallel in same direction.
374
+ - -1 if two lines are parallel in opposite direction.
375
+ - False otherwise.
314
376
"""
315
377
theta1 = np .arctan2 (dx1 , dy1 )
316
378
theta2 = np .arctan2 (dx2 , dy2 )
@@ -325,14 +387,14 @@ def check_if_parallel(dx1, dy1, dx2, dy2, tolerance=1.e-5):
325
387
326
388
def get_parallels (bezier2 , width ):
327
389
"""
328
- Given the quadratic bezier control points *bezier2*, returns
329
- control points of quadratic bezier lines roughly parallel to given
390
+ Given the quadratic Bezier control points *bezier2*, returns
391
+ control points of quadratic Bezier lines roughly parallel to given
330
392
one separated by *width*.
331
393
"""
332
394
333
- # The parallel bezier lines are constructed by following ways.
395
+ # The parallel Bezier lines are constructed by following ways.
334
396
# c1 and c2 are control points representing the begin and end of the
335
- # bezier line.
397
+ # Bezier line.
336
398
# cm is the middle point
337
399
338
400
c1x , c1y = bezier2 [0 ]
@@ -355,7 +417,7 @@ def get_parallels(bezier2, width):
355
417
356
418
# find c1_left, c1_right which are located along the lines
357
419
# through c1 and perpendicular to the tangential lines of the
358
- # bezier path at a distance of width. Same thing for c2_left and
420
+ # Bezier path at a distance of width. Same thing for c2_left and
359
421
# c2_right with respect to c2.
360
422
c1x_left , c1y_left , c1x_right , c1y_right = (
361
423
get_normal_points (c1x , c1y , cos_t1 , sin_t1 , width )
@@ -385,7 +447,7 @@ def get_parallels(bezier2, width):
385
447
sin_t1 , c2x_right , c2y_right ,
386
448
BC5F
cos_t2 , sin_t2 )
387
449
388
- # the parallel bezier lines are created with control points of
450
+ # the parallel Bezier lines are created with control points of
389
451
# [c1_left, cm_left, c2_left] and [c1_right, cm_right, c2_right]
390
452
path_left = [(c1x_left , c1y_left ),
391
453
(cmx_left , cmy_left ),
@@ -410,7 +472,7 @@ def find_control_points(c1x, c1y, mmx, mmy, c2x, c2y):
410
472
def make_wedged_bezier2 (bezier2 , width , w1 = 1. , wm = 0.5 , w2 = 0. ):
411
473
"""
412
474
Being similar to get_parallels, returns control points of two quadratic
413
- bezier lines having a width roughly parallel to given one separated by
475
+ Bezier lines having a width roughly parallel to given one separated by
414
476
*width*.
415
477
"""
416
478
@@ -426,7 +488,7 @@ def make_wedged_bezier2(bezier2, width, w1=1., wm=0.5, w2=0.):
426
488
427
489
# find c1_left, c1_right which are located along the lines
428
490
# through c1 and perpendicular to the tangential lines of the
429
- # bezier path at a distance of width. Same thing for c3_left and
491
+ # Bezier path at a distance of width. Same thing for c3_left and
430
492
# c3_right with respect to c3.
431
493
c1x_left , c1y_left , c1x_right , c1y_right = (
432
494
get_normal_points (c1x , c1y , cos_t1 , sin_t1 , width * w1 )
0 commit comments