simetri.geometry.bezier
Functions for working with Bezier curves. https://pomax.github.io/bezierinfo is a good resource for understanding Bezier curves.
1"""Functions for working with Bezier curves. 2https://pomax.github.io/bezierinfo is a good resource for understanding Bezier curves. 3""" 4 5from typing import Sequence 6from functools import lru_cache as memoize 7 8import numpy as np 9 10from ..graphics.shape import Shape 11from ..graphics.all_enums import Types 12from ..graphics.common import Point 13from ..helpers.utilities import find_closest_value 14from ..settings.settings import defaults 15from .geometry import ( 16 distance, 17 line_angle, 18 line_by_point_angle_length, 19 normal, 20 norm, 21 normalize, 22) 23 24array = np.array 25 26cubic_poly_matrix = np.array( 27 [[1, 0, 0, 0], [-3, 3, 0, 0], [3, -6, 3, 0], [-1, 3, -3, 1]] 28) 29 30quad_poly_matrix = np.array([[1, 0, 0], [-2, 2, 0], [1, -2, 1]]) 31 32 33class Bezier(Shape): 34 """A Bezier curve defined by control points. 35 36 For cubic Bezier curves: [V1, CP1, CP2, V2] 37 For quadratic Bezier curves: [V1, CP, V2] 38 Like all other geometry in simetri.graphics, 39 bezier curves are represented as a sequence of points. 40 Both quadratic and cubic bezier curves are supported. 41 Number of control points determines the type of Bezier curve. 42 A cubic Bezier curve has 4 control points, while a quadratic Bezier curve has 3. 43 curve.subtype reflects this as Types.BEZIER or Types.Q_BEZIER. 44 45 Attributes: 46 control_points (Sequence[Point]): Control points of the Bezier curve. 47 cubic (bool): True if the Bezier curve is cubic, False if quadratic. 48 matrix (array): Polynomial matrix for the Bezier curve. 49 """ 50 51 def __init__( 52 self, 53 control_points: Sequence[Point], 54 xform_matrix: array = None, 55 n_points=None, 56 **kwargs, 57 ) -> None: 58 """Initializes a Bezier curve. 59 60 Args: 61 control_points (Sequence[Point]): Control points of the Bezier curve. 62 xform_matrix (array, optional): Transformation matrix. Defaults to None. 63 n_points (int, optional): Number of points on the curve. Defaults to None. 64 **kwargs: Additional keyword arguments. 65 66 Raises: 67 ValueError: If the number of control points is not 3 or 4. 68 """ 69 if len(control_points) == 3: 70 if n_points is None: 71 n = defaults["n_bezier_points"] 72 else: 73 n = n_points 74 vertices = q_bezier_points(*control_points, n) 75 super().__init__( 76 vertices, subtype=Types.Q_BEZIER, xform_matrix=xform_matrix, **kwargs 77 ) 78 self.cubic = False 79 self.matrix = quad_poly_matrix @ array(control_points) 80 81 elif len(control_points) == 4: 82 if n_points is None: 83 n = defaults["n_bezier_points"] 84 else: 85 n = n_points 86 vertices = bezier_points(*control_points, n) 87 super().__init__( 88 vertices, subtype=Types.BEZIER, xform_matrix=xform_matrix, **kwargs 89 ) 90 self.cubic = True 91 self.matrix = cubic_poly_matrix @ array(control_points) 92 else: 93 raise ValueError("Invalid number of control points.") 94 self.control_points = control_points 95 96 @property 97 def control_points(self) -> Sequence[Point]: 98 """Return the control points of the Bezier curve. 99 100 Returns: 101 Sequence[Point]: Control points of the Bezier curve. 102 """ 103 return self.__dict__['control_points'] 104 105 @control_points.setter 106 def control_points(self, new_control_points: Sequence[Point]) -> None: 107 """Set new control points for the Bezier curve. 108 109 Args: 110 new_control_points (Sequence[Point]): New control points. 111 112 Raises: 113 ValueError: If the number of control points is not 3 or 4. 114 """ 115 self.control_points = new_control_points 116 if len(new_control_points) == 3: 117 vertices = q_bezier_points(*new_control_points) 118 self[:] = vertices 119 self.subtype = Types.Q_BEZIER 120 elif len(new_control_points) == 4: 121 vertices = bezier_points(*new_control_points) 122 self[:] = vertices 123 self.subtype = Types.BEZIER 124 else: 125 raise ValueError("Invalid number of control points.") 126 127 def copy(self) -> Shape: 128 """Return a copy of the Bezier curve. 129 130 Returns: 131 Shape: Copy of the Bezier curve. 132 """ 133 # to do: copy style and other attributes 134 copy_ = Bezier(self.control_points, xform_matrix=self.xform_matrix, n_points=len(self.vertices)) 135 136 return copy_ 137 138 def point(self, t: float): 139 """Return the point on the Bezier curve at t. 140 141 Args: 142 t (float): Parameter t, where 0 <= t <= 1. 143 144 Returns: 145 list: Point on the Bezier curve at t. 146 """ 147 # if self.cubic: 148 # np.array([t**3, t**2, t, 1]) @ self.matrix 149 # else: 150 # np.array([t**2, t, 1]) @ self.matrix 151 152 def point2(self, t: float): 153 """Return the point on the Bezier curve at t. 154 155 Args: 156 t (float): Parameter t, where 0 <= t <= 1. 157 158 Returns: 159 list: Point on the Bezier curve at t. 160 """ 161 p0, p1, p2, p3 = self.control_points 162 m = 1 - t 163 m2 = m * m 164 m3 = m2 * m 165 t2 = t * t 166 t3 = t2 * t 167 if self.cubic: 168 x = m3 * p0[0] + 3 * m2 * t * p1[0] + 3 * m * t2 * p2[0] + t3 * p3[0] 169 y = m3 * p0[1] + 3 * m2 * t * p1[1] + 3 * m * t2 * p2[1] + t3 * p3[1] 170 else: 171 x = m2 * p0[0] + 2 * m * t * p1[0] + t2 * p2[0] 172 y = m2 * p0[1] + 2 * m * t * p1[1] + t2 * p2[1] 173 174 return [x, y] 175 176 def derivative(self, t: float): 177 """Return the derivative of the Bezier curve at t. 178 179 Args: 180 t (float): Parameter t, where 0 <= t <= 1. 181 182 Returns: 183 list: Derivative of the Bezier curve at t. 184 """ 185 if self.cubic: 186 return get_cubic_derivative(t, self.control_points) 187 else: 188 return get_quadratic_derivative(t, self.control_points) 189 190 def normal(self, t: float): 191 """Return the normal of the Bezier curve at t. 192 193 Args: 194 t (float): Parameter t, where 0 <= t <= 1. 195 196 Returns: 197 list: Normal of the Bezier curve at t. 198 """ 199 d = self.derivative(t) 200 q = np.sqrt(d[0] * d[0] + d[1] * d[1]) 201 return [-d[1] / q, d[0] / q] 202 203 def tangent(self, t: float): 204 """Draw a unit tangent vector at t. 205 206 Args: 207 t (float): Parameter t, where 0 <= t <= 1. 208 209 Returns: 210 list: Unit tangent vector at t. 211 """ 212 d = self.derivative(t) 213 m = np.sqrt(d[0] * d[0] + d[1] * d[1]) 214 d = [d[0] / m, d[1] / m] 215 return d 216 217 218def equidistant_points(p0: Point, p1: Point, p2: Point, p3: Point, n_points=10): 219 """Return the points on a Bezier curve with equidistant spacing. 220 221 Args: 222 p0 (list): First control point. 223 p1 (list): Second control point. 224 p2 (list): Third control point. 225 p3 (list): Fourth control point. 226 n_points (int, optional): Number of points. Defaults to 10. 227 228 Returns: 229 tuple: Points on the Bezier curve, equidistant points, tangents, and normals. 230 """ 231 controls = [p0, p1, p2, p3] 232 n = 100 233 points = bezier_points(p0, p1, p2, p3, n) 234 tot = 0 235 seg_lengths = [0] 236 tangents = [norm((p1[0] - p0[0], p1[1] - p0[1]))] 237 normals = [normal(p0, p1)] 238 eq_points = [p0] 239 for i in range(1, n): 240 dist = distance(points[i - 1], points[i]) 241 tot += dist 242 seg_lengths.append(tot) 243 244 for i in range(1, n_points): 245 _, ind = find_closest_value(seg_lengths, i * tot / n_points) 246 pnt = points[ind] 247 eq_points.append(pnt) 248 d = get_cubic_derivative(ind / n, controls) 249 d1 = normalize(d) 250 p1 = pnt 251 p2 = get_normal(d) 252 tangents.append(d1) 253 normals.append(p2) 254 255 return points, eq_points, tangents, normals 256 257 258def offset_points(controls, offset, n_points, double=False): 259 """Return the points on the offset curve. 260 261 Args: 262 controls (list): Control points of the Bezier curve. 263 offset (float): Offset distance. 264 n_points (int): Number of points on the curve. 265 double (bool, optional): If True, return double offset points. Defaults to False. 266 267 Returns: 268 list: Points on the offset curve. 269 """ 270 n = 100 271 points = bezier_points(*controls, n_points=n) 272 tot = 0 273 seg_lengths = [0] 274 for i in range(1, n): 275 dist = distance(points[i - 1], points[i]) 276 tot += dist 277 seg_lengths.append(tot) 278 279 x, y = controls[0][:2] 280 np = normal(x, y) 281 x2, y2 = (x + offset * np[0], y + offset * np[1]) 282 offset_pnts = [(x2, y2)] 283 if double: 284 p1, p2 = mirror_point((x, y), (x2, y2)) 285 offset_pnts2 = [p2] 286 for i in range(1, n_points): 287 _, ind = find_closest_value(seg_lengths, i * tot / n) 288 pnt = points[ind] 289 d = get_cubic_derivative(ind / n, controls) 290 p1 = pnt 291 p2 = get_normal(d) 292 x2, y2 = p1[0] + offset * p2[0], p1[1] + offset * p2[1] 293 offset_pnts.append((x2, y2)) 294 if double: 295 _, p3 = mirror_point(pnt, (x2, y2)) 296 offset_pnts2.append(p3) 297 298 if double: 299 return offset_pnts, offset_pnts2 300 else: 301 return offset_pnts 302 303 304class BezierPoints(Shape): 305 """Points of a Bezier curve defined by the given control points. 306 307 These points are spaced evenly along the curve (unlike parametric points). 308 Normal and tangent unit vectors are also available at these points. 309 310 Attributes: 311 control_points (Sequence[Point]): Control points of the Bezier curve. 312 param_points (list): Parametric points on the Bezier curve. 313 tangents (list): Tangent vectors at the points. 314 normals (list): Normal vectors at the points. 315 n_points (int): Number of points on the curve. 316 """ 317 318 def __init__( 319 self, control_points: Sequence[Point], n_points: int = 10, **kwargs 320 ) -> None: 321 """Initializes Bezier points. 322 323 Args: 324 control_points (Sequence[Point]): Control points of the Bezier curve. 325 n_points (int, optional): Number of points on the curve. Defaults to 10. 326 **kwargs: Additional keyword arguments. 327 328 Raises: 329 ValueError: If the number of control points is not 3 or 4. 330 """ 331 if len(control_points) not in (4, 3): 332 raise ValueError("Invalid number of control points.") 333 334 param_points, vertices, tangents, normals = equidistant_points( 335 *control_points, n_points 336 ) 337 super().__init__(vertices, subtype=Types.BEZIER_POINTS, **kwargs) 338 self.control_points = control_points 339 self.param_points = param_points 340 self.tangents = tangents 341 self.normals = normals 342 self.n_points = n_points 343 344 def offsets(self, offset: float, double: bool=False): 345 """Return the points on the offset curve. 346 347 Args: 348 offset (float): Offset distance. 349 double (bool, optional): If True, return double offset points. Defaults to False. 350 351 Returns: 352 list: Points on the offset curve. 353 """ 354 offset_points1 = [] 355 if double: 356 offset_points2 = [] 357 for i, pnt in enumerate(self.vertices): 358 n_p = self.normals[i] 359 x2, y2 = pnt[0] + offset * n_p[0], pnt[1] + offset * n_p[1] 360 offset_points1.append((x2, y2)) 361 if double: 362 _, p3 = mirror_point((x2, y2), pnt) 363 offset_points2.append(p3) 364 365 if double: 366 return offset_points1, offset_points2 367 368 return offset_points1 369 370 371M = array([[1, 0, 0, 0], [-3, 3, 0, 0], [3, -6, 3, 0], [-1, 3, -3, 1]]) 372 373 374def bezier_points(p0, p1: Point, p2: Point, p3: Point, n_points=10): 375 """Return the points on a cubic Bezier curve. 376 377 Args: 378 p0 (list): First control point. 379 p1 (list): Second control point. 380 p2 (list): Third control point. 381 p3 (list): Fourth control point. 382 n_points (int, optional): Number of points. Defaults to 10. 383 384 Returns: 385 list: Points on the cubic Bezier curve. 386 387 Raises: 388 ValueError: If n_points is less than 5. 389 """ 390 if n_points < 5: 391 raise ValueError("n_points must be at least 5.") 392 393 n = n_points 394 f = np.ones(n) 395 t = np.linspace(0, 1, n) 396 t2 = t * t 397 t3 = t2 * t 398 T = np.column_stack((f, t, t2, t3)) 399 TM = T @ M 400 P = array([p0, p1, p2, p3]) 401 402 return TM @ P 403 404 405MQ = array([[1, 0, 0], [-2, 2, 0], [1, -2, 1]]) 406 407 408def q_bezier_points(p0: Point, p1: Point, p2: Point, n_points: int): 409 """Return the points on a quadratic Bezier curve. 410 411 Args: 412 p0 (list): First control point. 413 p1 (list): Second control point. 414 p2 (list): Third control point. 415 n_points (int): Number of points. 416 417 Returns: 418 list: Points on the quadratic Bezier curve. 419 420 Raises: 421 ValueError: If n_points is less than 5. 422 """ 423 if n_points < 5: 424 raise ValueError("n_points must be at least 5.") 425 426 n = n_points 427 f = np.ones(n) 428 t = np.linspace(0, 1, n) 429 t2 = t * t 430 T = np.column_stack((f, t, t2)) 431 TMQ = T @ MQ 432 P = array([p0, p1, p2]) 433 434 return TMQ @ P 435 436 437def split_bezier(p0: Point, p1: Point, p2: Point, p3: Point, z:float, n_points=10): 438 """Split a cubic Bezier curve at t=z. 439 440 Args: 441 p0 (list): First control point. 442 p1 (list): Second control point. 443 p2 (list): Third control point. 444 p3 (list): Fourth control point. 445 z (float): Parameter z, where 0 <= z <= 1. 446 n_points (int, optional): Number of points. Defaults to 10. 447 448 Returns: 449 tuple: Two Bezier curves split at t=z. 450 """ 451 p0 = array(p0) 452 p1 = array(p1) 453 p2 = array(p2) 454 p3 = array(p3) 455 bezier1 = [ 456 [p0], 457 [z * p1 - (z - 1) * p0], 458 [z**2 * p2 - 2 * z * (z - 1) * p1 + (z - 1) ** 2 * p0], 459 [ 460 z**3 * p3 461 - 3 * z**2 * (z - 1) * p2 462 + 3 * z * (z - 1) ** 2 * p1 463 - (z - 1) ** 3 * p0 464 ], 465 ] 466 467 bezier2 = [ 468 [z**3 * p0], 469 [3 * z**2 * (z - 1) * p1 - 3 * z * (z - 1) ** 2 * p0], 470 [3 * z * (z - 1) * p2 - 3 * (z - 1) ** 2 * p1], 471 [z * p3 - (z - 1) * p2], 472 ] 473 474 return Bezier(bezier1, n_points=n_points), Bezier(bezier2, n_points=n_points) 475 476 477def split_q_bezier(p0: Point, p1: Point, p2: Point, z:float, n_points=10): 478 """Split a quadratic Bezier curve at t=z. 479 480 Args: 481 p0 (list): First control point. 482 p1 (list): Second control point. 483 p2 (list): Third control point. 484 z (float): Parameter z, where 0 <= z <= 1. 485 n_points (int, optional): Number of points. Defaults to 10. 486 487 Returns: 488 tuple: Two Bezier curves split at t=z. 489 """ 490 p0 = array(p0) 491 p1 = array(p1) 492 p2 = array(p2) 493 bezier1 = [ 494 [p0], 495 [z * p1 - (z - 1) * p0], 496 [z**2 * p2 - 2 * z * (z - 1) * p1 + (z - 1) ** 2 * p0], 497 ] 498 499 bezier2 = [ 500 [z**2 * p0], 501 [2 * z * (z - 1) * p1 - (z - 1) ** 2 * p0], 502 [z * p2 - (z - 1) * p1], 503 ] 504 505 return Bezier(bezier1, n_points=n_points), Bezier(bezier2, n_points=n_points) 506 507 508def mirror_point(cp: Point, vertex: Point): 509 """Return the mirror of cp about vertex. 510 511 Args: 512 cp (list): Control point to be mirrored. 513 vertex (list): Vertex point. 514 515 Returns: 516 list: Mirrored control point. 517 """ 518 length = distance(cp, vertex) 519 angle = line_angle(cp, vertex) 520 cp2 = line_by_point_angle_length(vertex, angle, length) 521 return cp2 522 523 524def curve(v1: Point, c1: Point, c2: Point, v2: Point, *args, **kwargs): 525 """Return a cubic Bezier curve/s. 526 527 Args: 528 v1 (list): First vertex. 529 c1 (list): First control point. 530 c2 (list): Second control point. 531 v2 (list): Second vertex. 532 *args: Additional control points and vertices. 533 **kwargs: Additional keyword arguments. 534 535 Returns: 536 list: List of cubic Bezier curves. 537 538 Raises: 539 ValueError: If the number of control points is invalid. 540 """ 541 curves = [Bezier([v1, c1, c2, v2], **kwargs)] 542 last_vertex = v2 543 for arg in args: 544 if len(arg) == 2: 545 c3 = mirror_point(c2, v2) 546 v3 = arg[1] 547 c4 = arg[0] 548 curves.append(Bezier([last_vertex, c3, c4, v3], **kwargs)) 549 last_vertex = v3 550 elif len(arg) == 3: 551 c3 = arg[0] 552 c4 = arg[1] 553 v3 = arg[2] 554 curves.append(Bezier([last_vertex, c3, c4, v3], **kwargs)) 555 last_vertex = v3 556 else: 557 raise ValueError("Invalid number of control points.") 558 559 return curves 560 561 562def q_curve(v1: Point, c: Point, v2: Point, *args, **kwargs): 563 """Return a quadratic Bezier curve/s. 564 565 Args: 566 v1 (list): First vertex. 567 c (list): Control point. 568 v2 (list): Second vertex. 569 *args: Additional control points and vertices. 570 **kwargs: Additional keyword arguments. 571 572 Returns: 573 list: List of quadratic Bezier curves. 574 575 Raises: 576 ValueError: If the number of control points is invalid. 577 """ 578 curves = [Bezier([v1, c, v2], **kwargs)] 579 last_vertex = v2 580 for arg in args: 581 if len(arg) == 1: 582 c3 = mirror_point(c, v2) 583 v3 = arg[0] 584 curves.append(Bezier([last_vertex, c3, v3], **kwargs)) 585 last_vertex = v3 586 elif len(arg) == 2: 587 c3 = arg[0] 588 v3 = arg[1] 589 curves.append(Bezier([last_vertex, c3, v3], **kwargs)) 590 last_vertex = v3 591 else: 592 raise ValueError("Invalid number of control points.") 593 594 return curves 595 596 597def get_quadratic_derivative(t: float, points: Sequence[Point]): 598 """Return the derivative of a quadratic Bezier curve at t. 599 600 Args: 601 t (float): Parameter t, where 0 <= t <= 1. 602 points (list): Control points of the Bezier curve. 603 604 Returns: 605 list: Derivative of the quadratic Bezier curve at t. 606 """ 607 mt = 1 - t 608 d = [ 609 2 * (points[1][0] - points[0][0]), 610 2 * (points[1][1] - points[0][1]), 611 2 * (points[2][0] - points[1][0]), 612 2 * (points[2][1] - points[1][1]), 613 ] 614 615 return [mt * d[0] + t * d[2], mt * d[1] + t * d[3]] 616 617 618def get_cubic_derivative(t: float, points: Sequence[Point]): 619 """Return the derivative of a cubic Bezier curve at t. 620 621 Args: 622 t (float): Parameter t, where 0 <= t <= 1. 623 points (list): Control points of the Bezier curve. 624 625 Returns: 626 list: Derivative of the cubic Bezier curve at t. 627 """ 628 mt = 1 - t 629 a = mt * mt 630 b = 2 * mt * t 631 c = t * t 632 d = [ 633 3 * (points[1][0] - points[0][0]), 634 3 * (points[1][1] - points[0][1]), 635 3 * (points[2][0] - points[1][0]), 636 3 * (points[2][1] - points[1][1]), 637 3 * (points[3][0] - points[2][0]), 638 3 * (points[3][1] - points[2][1]), 639 ] 640 641 return [a * d[0] + b * d[2] + c * d[4], a * d[1] + b * d[3] + c * d[5]] 642 643 644def get_normal(d: Sequence[float]): 645 """Return the normal of a given line. 646 647 Args: 648 d (list): Derivative of the line. 649 650 Returns: 651 list: Normal of the line. 652 """ 653 q = np.sqrt(d[0] * d[0] + d[1] * d[1]) 654 return [-d[1] / q, d[0] / q]
array(object, dtype=None, *, copy=True, order='K', subok=False, ndmin=0, like=None)
Create an array.
Parameters
object : array_like
An array, any object exposing the array interface, an object whose
__array__
method returns an array, or any (nested) sequence.
If object is a scalar, a 0-dimensional array containing object is
returned.
dtype : data-type, optional
The desired data-type for the array. If not given, NumPy will try to use
a default dtype
that can represent the values (by applying promotion
rules when necessary.)
copy : bool, optional
If True
(default), then the array data is copied. If None
,
a copy will only be made if __array__
returns a copy, if obj is
a nested sequence, or if a copy is needed to satisfy any of the other
requirements (dtype
, order
, etc.). Note that any copy of
the data is shallow, i.e., for arrays with object dtype, the new
array will point to the same objects. See Examples for ndarray.copy
.
For False
it raises a ValueError
if a copy cannot be avoided.
Default: True
.
order : {'K', 'A', 'C', 'F'}, optional
Specify the memory layout of the array. If object is not an array, the
newly created array will be in C order (row major) unless 'F' is
specified, in which case it will be in Fortran order (column major).
If object is an array the following holds.
===== ========= ===================================================
order no copy copy=True
===== ========= ===================================================
'K' unchanged F & C order preserved, otherwise most similar order
'A' unchanged F order if input is F and not C, otherwise C order
'C' C order C order
'F' F order F order
===== ========= ===================================================
When ``copy=None`` and a copy is made for other reasons, the result is
the same as if ``copy=True``, with some exceptions for 'A', see the
Notes section. The default order is 'K'.
subok : bool, optional
If True, then sub-classes will be passed-through, otherwise
the returned array will be forced to be a base-class array (default).
ndmin : int, optional
Specifies the minimum number of dimensions that the resulting
array should have. Ones will be prepended to the shape as
needed to meet this requirement.
like : array_like, optional
Reference object to allow the creation of arrays which are not
NumPy arrays. If an array-like passed in as like
supports
the __array_function__
protocol, the result will be defined
by it. In this case, it ensures the creation of an array object
compatible with that passed in via this argument.
*New in version 1.20.0.*
Returns
out : ndarray An array object satisfying the specified requirements.
See Also
empty_like : Return an empty array with shape and type of input. ones_like : Return an array of ones with shape and type of input. zeros_like : Return an array of zeros with shape and type of input. full_like : Return a new array with shape of input filled with value. empty : Return a new uninitialized array. ones : Return a new array setting values to one. zeros : Return a new array setting values to zero. full : Return a new array of given shape filled with value. copy: Return an array copy of the given object.
Notes
When order is 'A' and object
is an array in neither 'C' nor 'F' order,
and a copy is forced by a change in dtype, then the order of the result is
not necessarily 'C' as expected. This is likely a bug.
Examples
>>> np.array([1, 2, 3])
array([1, 2, 3])
Upcasting:
>>> np.array([1, 2, 3.0])
array([ 1., 2., 3.])
More than one dimension:
>>> np.array([[1, 2], [3, 4]])
array([[1, 2],
[3, 4]])
Minimum dimensions 2:
>>> np.array([1, 2, 3], ndmin=2)
array([[1, 2, 3]])
Type provided:
>>> np.array([1, 2, 3], dtype=complex)
array([ 1.+0.j, 2.+0.j, 3.+0.j])
Data-type consisting of more than one element:
>>> x = np.array([(1,2),(3,4)],dtype=[('a','<i4'),('b','<i4')])
>>> x['a']
array([1, 3])
Creating an array from sub-classes:
>>> np.array(np.asmatrix('1 2; 3 4'))
array([[1, 2],
[3, 4]])
>>> np.array(np.asmatrix('1 2; 3 4'), subok=True)
matrix([[1, 2],
[3, 4]])
34class Bezier(Shape): 35 """A Bezier curve defined by control points. 36 37 For cubic Bezier curves: [V1, CP1, CP2, V2] 38 For quadratic Bezier curves: [V1, CP, V2] 39 Like all other geometry in simetri.graphics, 40 bezier curves are represented as a sequence of points. 41 Both quadratic and cubic bezier curves are supported. 42 Number of control points determines the type of Bezier curve. 43 A cubic Bezier curve has 4 control points, while a quadratic Bezier curve has 3. 44 curve.subtype reflects this as Types.BEZIER or Types.Q_BEZIER. 45 46 Attributes: 47 control_points (Sequence[Point]): Control points of the Bezier curve. 48 cubic (bool): True if the Bezier curve is cubic, False if quadratic. 49 matrix (array): Polynomial matrix for the Bezier curve. 50 """ 51 52 def __init__( 53 self, 54 control_points: Sequence[Point], 55 xform_matrix: array = None, 56 n_points=None, 57 **kwargs, 58 ) -> None: 59 """Initializes a Bezier curve. 60 61 Args: 62 control_points (Sequence[Point]): Control points of the Bezier curve. 63 xform_matrix (array, optional): Transformation matrix. Defaults to None. 64 n_points (int, optional): Number of points on the curve. Defaults to None. 65 **kwargs: Additional keyword arguments. 66 67 Raises: 68 ValueError: If the number of control points is not 3 or 4. 69 """ 70 if len(control_points) == 3: 71 if n_points is None: 72 n = defaults["n_bezier_points"] 73 else: 74 n = n_points 75 vertices = q_bezier_points(*control_points, n) 76 super().__init__( 77 vertices, subtype=Types.Q_BEZIER, xform_matrix=xform_matrix, **kwargs 78 ) 79 self.cubic = False 80 self.matrix = quad_poly_matrix @ array(control_points) 81 82 elif len(control_points) == 4: 83 if n_points is None: 84 n = defaults["n_bezier_points"] 85 else: 86 n = n_points 87 vertices = bezier_points(*control_points, n) 88 super().__init__( 89 vertices, subtype=Types.BEZIER, xform_matrix=xform_matrix, **kwargs 90 ) 91 self.cubic = True 92 self.matrix = cubic_poly_matrix @ array(control_points) 93 else: 94 raise ValueError("Invalid number of control points.") 95 self.control_points = control_points 96 97 @property 98 def control_points(self) -> Sequence[Point]: 99 """Return the control points of the Bezier curve. 100 101 Returns: 102 Sequence[Point]: Control points of the Bezier curve. 103 """ 104 return self.__dict__['control_points'] 105 106 @control_points.setter 107 def control_points(self, new_control_points: Sequence[Point]) -> None: 108 """Set new control points for the Bezier curve. 109 110 Args: 111 new_control_points (Sequence[Point]): New control points. 112 113 Raises: 114 ValueError: If the number of control points is not 3 or 4. 115 """ 116 self.control_points = new_control_points 117 if len(new_control_points) == 3: 118 vertices = q_bezier_points(*new_control_points) 119 self[:] = vertices 120 self.subtype = Types.Q_BEZIER 121 elif len(new_control_points) == 4: 122 vertices = bezier_points(*new_control_points) 123 self[:] = vertices 124 self.subtype = Types.BEZIER 125 else: 126 raise ValueError("Invalid number of control points.") 127 128 def copy(self) -> Shape: 129 """Return a copy of the Bezier curve. 130 131 Returns: 132 Shape: Copy of the Bezier curve. 133 """ 134 # to do: copy style and other attributes 135 copy_ = Bezier(self.control_points, xform_matrix=self.xform_matrix, n_points=len(self.vertices)) 136 137 return copy_ 138 139 def point(self, t: float): 140 """Return the point on the Bezier curve at t. 141 142 Args: 143 t (float): Parameter t, where 0 <= t <= 1. 144 145 Returns: 146 list: Point on the Bezier curve at t. 147 """ 148 # if self.cubic: 149 # np.array([t**3, t**2, t, 1]) @ self.matrix 150 # else: 151 # np.array([t**2, t, 1]) @ self.matrix 152 153 def point2(self, t: float): 154 """Return the point on the Bezier curve at t. 155 156 Args: 157 t (float): Parameter t, where 0 <= t <= 1. 158 159 Returns: 160 list: Point on the Bezier curve at t. 161 """ 162 p0, p1, p2, p3 = self.control_points 163 m = 1 - t 164 m2 = m * m 165 m3 = m2 * m 166 t2 = t * t 167 t3 = t2 * t 168 if self.cubic: 169 x = m3 * p0[0] + 3 * m2 * t * p1[0] + 3 * m * t2 * p2[0] + t3 * p3[0] 170 y = m3 * p0[1] + 3 * m2 * t * p1[1] + 3 * m * t2 * p2[1] + t3 * p3[1] 171 else: 172 x = m2 * p0[0] + 2 * m * t * p1[0] + t2 * p2[0] 173 y = m2 * p0[1] + 2 * m * t * p1[1] + t2 * p2[1] 174 175 return [x, y] 176 177 def derivative(self, t: float): 178 """Return the derivative of the Bezier curve at t. 179 180 Args: 181 t (float): Parameter t, where 0 <= t <= 1. 182 183 Returns: 184 list: Derivative of the Bezier curve at t. 185 """ 186 if self.cubic: 187 return get_cubic_derivative(t, self.control_points) 188 else: 189 return get_quadratic_derivative(t, self.control_points) 190 191 def normal(self, t: float): 192 """Return the normal of the Bezier curve at t. 193 194 Args: 195 t (float): Parameter t, where 0 <= t <= 1. 196 197 Returns: 198 list: Normal of the Bezier curve at t. 199 """ 200 d = self.derivative(t) 201 q = np.sqrt(d[0] * d[0] + d[1] * d[1]) 202 return [-d[1] / q, d[0] / q] 203 204 def tangent(self, t: float): 205 """Draw a unit tangent vector at t. 206 207 Args: 208 t (float): Parameter t, where 0 <= t <= 1. 209 210 Returns: 211 list: Unit tangent vector at t. 212 """ 213 d = self.derivative(t) 214 m = np.sqrt(d[0] * d[0] + d[1] * d[1]) 215 d = [d[0] / m, d[1] / m] 216 return d
A Bezier curve defined by control points.
For cubic Bezier curves: [V1, CP1, CP2, V2] For quadratic Bezier curves: [V1, CP, V2] Like all other geometry in simetri.graphics, bezier curves are represented as a sequence of points. Both quadratic and cubic bezier curves are supported. Number of control points determines the type of Bezier curve. A cubic Bezier curve has 4 control points, while a quadratic Bezier curve has 3. curve.subtype reflects this as Types.BEZIER or Types.Q_BEZIER.
Attributes:
- control_points (Sequence[Point]): Control points of the Bezier curve.
- cubic (bool): True if the Bezier curve is cubic, False if quadratic.
- matrix (array): Polynomial matrix for the Bezier curve.
52 def __init__( 53 self, 54 control_points: Sequence[Point], 55 xform_matrix: array = None, 56 n_points=None, 57 **kwargs, 58 ) -> None: 59 """Initializes a Bezier curve. 60 61 Args: 62 control_points (Sequence[Point]): Control points of the Bezier curve. 63 xform_matrix (array, optional): Transformation matrix. Defaults to None. 64 n_points (int, optional): Number of points on the curve. Defaults to None. 65 **kwargs: Additional keyword arguments. 66 67 Raises: 68 ValueError: If the number of control points is not 3 or 4. 69 """ 70 if len(control_points) == 3: 71 if n_points is None: 72 n = defaults["n_bezier_points"] 73 else: 74 n = n_points 75 vertices = q_bezier_points(*control_points, n) 76 super().__init__( 77 vertices, subtype=Types.Q_BEZIER, xform_matrix=xform_matrix, **kwargs 78 ) 79 self.cubic = False 80 self.matrix = quad_poly_matrix @ array(control_points) 81 82 elif len(control_points) == 4: 83 if n_points is None: 84 n = defaults["n_bezier_points"] 85 else: 86 n = n_points 87 vertices = bezier_points(*control_points, n) 88 super().__init__( 89 vertices, subtype=Types.BEZIER, xform_matrix=xform_matrix, **kwargs 90 ) 91 self.cubic = True 92 self.matrix = cubic_poly_matrix @ array(control_points) 93 else: 94 raise ValueError("Invalid number of control points.") 95 self.control_points = control_points
Initializes a Bezier curve.
Arguments:
- control_points (Sequence[Point]): Control points of the Bezier curve.
- xform_matrix (array, optional): Transformation matrix. Defaults to None.
- n_points (int, optional): Number of points on the curve. Defaults to None.
- **kwargs: Additional keyword arguments.
Raises:
- ValueError: If the number of control points is not 3 or 4.
97 @property 98 def control_points(self) -> Sequence[Point]: 99 """Return the control points of the Bezier curve. 100 101 Returns: 102 Sequence[Point]: Control points of the Bezier curve. 103 """ 104 return self.__dict__['control_points']
Return the control points of the Bezier curve.
Returns:
Sequence[Point]: Control points of the Bezier curve.
128 def copy(self) -> Shape: 129 """Return a copy of the Bezier curve. 130 131 Returns: 132 Shape: Copy of the Bezier curve. 133 """ 134 # to do: copy style and other attributes 135 copy_ = Bezier(self.control_points, xform_matrix=self.xform_matrix, n_points=len(self.vertices)) 136 137 return copy_
Return a copy of the Bezier curve.
Returns:
Shape: Copy of the Bezier curve.
139 def point(self, t: float): 140 """Return the point on the Bezier curve at t. 141 142 Args: 143 t (float): Parameter t, where 0 <= t <= 1. 144 145 Returns: 146 list: Point on the Bezier curve at t. 147 """ 148 # if self.cubic: 149 # np.array([t**3, t**2, t, 1]) @ self.matrix 150 # else: 151 # np.array([t**2, t, 1]) @ self.matrix
Return the point on the Bezier curve at t.
Arguments:
- t (float): Parameter t, where 0 <= t <= 1.
Returns:
list: Point on the Bezier curve at t.
153 def point2(self, t: float): 154 """Return the point on the Bezier curve at t. 155 156 Args: 157 t (float): Parameter t, where 0 <= t <= 1. 158 159 Returns: 160 list: Point on the Bezier curve at t. 161 """ 162 p0, p1, p2, p3 = self.control_points 163 m = 1 - t 164 m2 = m * m 165 m3 = m2 * m 166 t2 = t * t 167 t3 = t2 * t 168 if self.cubic: 169 x = m3 * p0[0] + 3 * m2 * t * p1[0] + 3 * m * t2 * p2[0] + t3 * p3[0] 170 y = m3 * p0[1] + 3 * m2 * t * p1[1] + 3 * m * t2 * p2[1] + t3 * p3[1] 171 else: 172 x = m2 * p0[0] + 2 * m * t * p1[0] + t2 * p2[0] 173 y = m2 * p0[1] + 2 * m * t * p1[1] + t2 * p2[1] 174 175 return [x, y]
Return the point on the Bezier curve at t.
Arguments:
- t (float): Parameter t, where 0 <= t <= 1.
Returns:
list: Point on the Bezier curve at t.
177 def derivative(self, t: float): 178 """Return the derivative of the Bezier curve at t. 179 180 Args: 181 t (float): Parameter t, where 0 <= t <= 1. 182 183 Returns: 184 list: Derivative of the Bezier curve at t. 185 """ 186 if self.cubic: 187 return get_cubic_derivative(t, self.control_points) 188 else: 189 return get_quadratic_derivative(t, self.control_points)
Return the derivative of the Bezier curve at t.
Arguments:
- t (float): Parameter t, where 0 <= t <= 1.
Returns:
list: Derivative of the Bezier curve at t.
191 def normal(self, t: float): 192 """Return the normal of the Bezier curve at t. 193 194 Args: 195 t (float): Parameter t, where 0 <= t <= 1. 196 197 Returns: 198 list: Normal of the Bezier curve at t. 199 """ 200 d = self.derivative(t) 201 q = np.sqrt(d[0] * d[0] + d[1] * d[1]) 202 return [-d[1] / q, d[0] / q]
Return the normal of the Bezier curve at t.
Arguments:
- t (float): Parameter t, where 0 <= t <= 1.
Returns:
list: Normal of the Bezier curve at t.
204 def tangent(self, t: float): 205 """Draw a unit tangent vector at t. 206 207 Args: 208 t (float): Parameter t, where 0 <= t <= 1. 209 210 Returns: 211 list: Unit tangent vector at t. 212 """ 213 d = self.derivative(t) 214 m = np.sqrt(d[0] * d[0] + d[1] * d[1]) 215 d = [d[0] / m, d[1] / m] 216 return d
Draw a unit tangent vector at t.
Arguments:
- t (float): Parameter t, where 0 <= t <= 1.
Returns:
list: Unit tangent vector at t.
Inherited Members
219def equidistant_points(p0: Point, p1: Point, p2: Point, p3: Point, n_points=10): 220 """Return the points on a Bezier curve with equidistant spacing. 221 222 Args: 223 p0 (list): First control point. 224 p1 (list): Second control point. 225 p2 (list): Third control point. 226 p3 (list): Fourth control point. 227 n_points (int, optional): Number of points. Defaults to 10. 228 229 Returns: 230 tuple: Points on the Bezier curve, equidistant points, tangents, and normals. 231 """ 232 controls = [p0, p1, p2, p3] 233 n = 100 234 points = bezier_points(p0, p1, p2, p3, n) 235 tot = 0 236 seg_lengths = [0] 237 tangents = [norm((p1[0] - p0[0], p1[1] - p0[1]))] 238 normals = [normal(p0, p1)] 239 eq_points = [p0] 240 for i in range(1, n): 241 dist = distance(points[i - 1], points[i]) 242 tot += dist 243 seg_lengths.append(tot) 244 245 for i in range(1, n_points): 246 _, ind = find_closest_value(seg_lengths, i * tot / n_points) 247 pnt = points[ind] 248 eq_points.append(pnt) 249 d = get_cubic_derivative(ind / n, controls) 250 d1 = normalize(d) 251 p1 = pnt 252 p2 = get_normal(d) 253 tangents.append(d1) 254 normals.append(p2) 255 256 return points, eq_points, tangents, normals
Return the points on a Bezier curve with equidistant spacing.
Arguments:
- p0 (list): First control point.
- p1 (list): Second control point.
- p2 (list): Third control point.
- p3 (list): Fourth control point.
- n_points (int, optional): Number of points. Defaults to 10.
Returns:
tuple: Points on the Bezier curve, equidistant points, tangents, and normals.
259def offset_points(controls, offset, n_points, double=False): 260 """Return the points on the offset curve. 261 262 Args: 263 controls (list): Control points of the Bezier curve. 264 offset (float): Offset distance. 265 n_points (int): Number of points on the curve. 266 double (bool, optional): If True, return double offset points. Defaults to False. 267 268 Returns: 269 list: Points on the offset curve. 270 """ 271 n = 100 272 points = bezier_points(*controls, n_points=n) 273 tot = 0 274 seg_lengths = [0] 275 for i in range(1, n): 276 dist = distance(points[i - 1], points[i]) 277 tot += dist 278 seg_lengths.append(tot) 279 280 x, y = controls[0][:2] 281 np = normal(x, y) 282 x2, y2 = (x + offset * np[0], y + offset * np[1]) 283 offset_pnts = [(x2, y2)] 284 if double: 285 p1, p2 = mirror_point((x, y), (x2, y2)) 286 offset_pnts2 = [p2] 287 for i in range(1, n_points): 288 _, ind = find_closest_value(seg_lengths, i * tot / n) 289 pnt = points[ind] 290 d = get_cubic_derivative(ind / n, controls) 291 p1 = pnt 292 p2 = get_normal(d) 293 x2, y2 = p1[0] + offset * p2[0], p1[1] + offset * p2[1] 294 offset_pnts.append((x2, y2)) 295 if double: 296 _, p3 = mirror_point(pnt, (x2, y2)) 297 offset_pnts2.append(p3) 298 299 if double: 300 return offset_pnts, offset_pnts2 301 else: 302 return offset_pnts
Return the points on the offset curve.
Arguments:
- controls (list): Control points of the Bezier curve.
- offset (float): Offset distance.
- n_points (int): Number of points on the curve.
- double (bool, optional): If True, return double offset points. Defaults to False.
Returns:
list: Points on the offset curve.
305class BezierPoints(Shape): 306 """Points of a Bezier curve defined by the given control points. 307 308 These points are spaced evenly along the curve (unlike parametric points). 309 Normal and tangent unit vectors are also available at these points. 310 311 Attributes: 312 control_points (Sequence[Point]): Control points of the Bezier curve. 313 param_points (list): Parametric points on the Bezier curve. 314 tangents (list): Tangent vectors at the points. 315 normals (list): Normal vectors at the points. 316 n_points (int): Number of points on the curve. 317 """ 318 319 def __init__( 320 self, control_points: Sequence[Point], n_points: int = 10, **kwargs 321 ) -> None: 322 """Initializes Bezier points. 323 324 Args: 325 control_points (Sequence[Point]): Control points of the Bezier curve. 326 n_points (int, optional): Number of points on the curve. Defaults to 10. 327 **kwargs: Additional keyword arguments. 328 329 Raises: 330 ValueError: If the number of control points is not 3 or 4. 331 """ 332 if len(control_points) not in (4, 3): 333 raise ValueError("Invalid number of control points.") 334 335 param_points, vertices, tangents, normals = equidistant_points( 336 *control_points, n_points 337 ) 338 super().__init__(vertices, subtype=Types.BEZIER_POINTS, **kwargs) 339 self.control_points = control_points 340 self.param_points = param_points 341 self.tangents = tangents 342 self.normals = normals 343 self.n_points = n_points 344 345 def offsets(self, offset: float, double: bool=False): 346 """Return the points on the offset curve. 347 348 Args: 349 offset (float): Offset distance. 350 double (bool, optional): If True, return double offset points. Defaults to False. 351 352 Returns: 353 list: Points on the offset curve. 354 """ 355 offset_points1 = [] 356 if double: 357 offset_points2 = [] 358 for i, pnt in enumerate(self.vertices): 359 n_p = self.normals[i] 360 x2, y2 = pnt[0] + offset * n_p[0], pnt[1] + offset * n_p[1] 361 offset_points1.append((x2, y2)) 362 if double: 363 _, p3 = mirror_point((x2, y2), pnt) 364 offset_points2.append(p3) 365 366 if double: 367 return offset_points1, offset_points2 368 369 return offset_points1
Points of a Bezier curve defined by the given control points.
These points are spaced evenly along the curve (unlike parametric points). Normal and tangent unit vectors are also available at these points.
Attributes:
- control_points (Sequence[Point]): Control points of the Bezier curve.
- param_points (list): Parametric points on the Bezier curve.
- tangents (list): Tangent vectors at the points.
- normals (list): Normal vectors at the points.
- n_points (int): Number of points on the curve.
319 def __init__( 320 self, control_points: Sequence[Point], n_points: int = 10, **kwargs 321 ) -> None: 322 """Initializes Bezier points. 323 324 Args: 325 control_points (Sequence[Point]): Control points of the Bezier curve. 326 n_points (int, optional): Number of points on the curve. Defaults to 10. 327 **kwargs: Additional keyword arguments. 328 329 Raises: 330 ValueError: If the number of control points is not 3 or 4. 331 """ 332 if len(control_points) not in (4, 3): 333 raise ValueError("Invalid number of control points.") 334 335 param_points, vertices, tangents, normals = equidistant_points( 336 *control_points, n_points 337 ) 338 super().__init__(vertices, subtype=Types.BEZIER_POINTS, **kwargs) 339 self.control_points = control_points 340 self.param_points = param_points 341 self.tangents = tangents 342 self.normals = normals 343 self.n_points = n_points
Initializes Bezier points.
Arguments:
- control_points (Sequence[Point]): Control points of the Bezier curve.
- n_points (int, optional): Number of points on the curve. Defaults to 10.
- **kwargs: Additional keyword arguments.
Raises:
- ValueError: If the number of control points is not 3 or 4.
345 def offsets(self, offset: float, double: bool=False): 346 """Return the points on the offset curve. 347 348 Args: 349 offset (float): Offset distance. 350 double (bool, optional): If True, return double offset points. Defaults to False. 351 352 Returns: 353 list: Points on the offset curve. 354 """ 355 offset_points1 = [] 356 if double: 357 offset_points2 = [] 358 for i, pnt in enumerate(self.vertices): 359 n_p = self.normals[i] 360 x2, y2 = pnt[0] + offset * n_p[0], pnt[1] + offset * n_p[1] 361 offset_points1.append((x2, y2)) 362 if double: 363 _, p3 = mirror_point((x2, y2), pnt) 364 offset_points2.append(p3) 365 366 if double: 367 return offset_points1, offset_points2 368 369 return offset_points1
Return the points on the offset curve.
Arguments:
- offset (float): Offset distance.
- double (bool, optional): If True, return double offset points. Defaults to False.
Returns:
list: Points on the offset curve.
Inherited Members
375def bezier_points(p0, p1: Point, p2: Point, p3: Point, n_points=10): 376 """Return the points on a cubic Bezier curve. 377 378 Args: 379 p0 (list): First control point. 380 p1 (list): Second control point. 381 p2 (list): Third control point. 382 p3 (list): Fourth control point. 383 n_points (int, optional): Number of points. Defaults to 10. 384 385 Returns: 386 list: Points on the cubic Bezier curve. 387 388 Raises: 389 ValueError: If n_points is less than 5. 390 """ 391 if n_points < 5: 392 raise ValueError("n_points must be at least 5.") 393 394 n = n_points 395 f = np.ones(n) 396 t = np.linspace(0, 1, n) 397 t2 = t * t 398 t3 = t2 * t 399 T = np.column_stack((f, t, t2, t3)) 400 TM = T @ M 401 P = array([p0, p1, p2, p3]) 402 403 return TM @ P
Return the points on a cubic Bezier curve.
Arguments:
- p0 (list): First control point.
- p1 (list): Second control point.
- p2 (list): Third control point.
- p3 (list): Fourth control point.
- n_points (int, optional): Number of points. Defaults to 10.
Returns:
list: Points on the cubic Bezier curve.
Raises:
- ValueError: If n_points is less than 5.
409def q_bezier_points(p0: Point, p1: Point, p2: Point, n_points: int): 410 """Return the points on a quadratic Bezier curve. 411 412 Args: 413 p0 (list): First control point. 414 p1 (list): Second control point. 415 p2 (list): Third control point. 416 n_points (int): Number of points. 417 418 Returns: 419 list: Points on the quadratic Bezier curve. 420 421 Raises: 422 ValueError: If n_points is less than 5. 423 """ 424 if n_points < 5: 425 raise ValueError("n_points must be at least 5.") 426 427 n = n_points 428 f = np.ones(n) 429 t = np.linspace(0, 1, n) 430 t2 = t * t 431 T = np.column_stack((f, t, t2)) 432 TMQ = T @ MQ 433 P = array([p0, p1, p2]) 434 435 return TMQ @ P
Return the points on a quadratic Bezier curve.
Arguments:
- p0 (list): First control point.
- p1 (list): Second control point.
- p2 (list): Third control point.
- n_points (int): Number of points.
Returns:
list: Points on the quadratic Bezier curve.
Raises:
- ValueError: If n_points is less than 5.
438def split_bezier(p0: Point, p1: Point, p2: Point, p3: Point, z:float, n_points=10): 439 """Split a cubic Bezier curve at t=z. 440 441 Args: 442 p0 (list): First control point. 443 p1 (list): Second control point. 444 p2 (list): Third control point. 445 p3 (list): Fourth control point. 446 z (float): Parameter z, where 0 <= z <= 1. 447 n_points (int, optional): Number of points. Defaults to 10. 448 449 Returns: 450 tuple: Two Bezier curves split at t=z. 451 """ 452 p0 = array(p0) 453 p1 = array(p1) 454 p2 = array(p2) 455 p3 = array(p3) 456 bezier1 = [ 457 [p0], 458 [z * p1 - (z - 1) * p0], 459 [z**2 * p2 - 2 * z * (z - 1) * p1 + (z - 1) ** 2 * p0], 460 [ 461 z**3 * p3 462 - 3 * z**2 * (z - 1) * p2 463 + 3 * z * (z - 1) ** 2 * p1 464 - (z - 1) ** 3 * p0 465 ], 466 ] 467 468 bezier2 = [ 469 [z**3 * p0], 470 [3 * z**2 * (z - 1) * p1 - 3 * z * (z - 1) ** 2 * p0], 471 [3 * z * (z - 1) * p2 - 3 * (z - 1) ** 2 * p1], 472 [z * p3 - (z - 1) * p2], 473 ] 474 475 return Bezier(bezier1, n_points=n_points), Bezier(bezier2, n_points=n_points)
Split a cubic Bezier curve at t=z.
Arguments:
- p0 (list): First control point.
- p1 (list): Second control point.
- p2 (list): Third control point.
- p3 (list): Fourth control point.
- z (float): Parameter z, where 0 <= z <= 1.
- n_points (int, optional): Number of points. Defaults to 10.
Returns:
tuple: Two Bezier curves split at t=z.
478def split_q_bezier(p0: Point, p1: Point, p2: Point, z:float, n_points=10): 479 """Split a quadratic Bezier curve at t=z. 480 481 Args: 482 p0 (list): First control point. 483 p1 (list): Second control point. 484 p2 (list): Third control point. 485 z (float): Parameter z, where 0 <= z <= 1. 486 n_points (int, optional): Number of points. Defaults to 10. 487 488 Returns: 489 tuple: Two Bezier curves split at t=z. 490 """ 491 p0 = array(p0) 492 p1 = array(p1) 493 p2 = array(p2) 494 bezier1 = [ 495 [p0], 496 [z * p1 - (z - 1) * p0], 497 [z**2 * p2 - 2 * z * (z - 1) * p1 + (z - 1) ** 2 * p0], 498 ] 499 500 bezier2 = [ 501 [z**2 * p0], 502 [2 * z * (z - 1) * p1 - (z - 1) ** 2 * p0], 503 [z * p2 - (z - 1) * p1], 504 ] 505 506 return Bezier(bezier1, n_points=n_points), Bezier(bezier2, n_points=n_points)
Split a quadratic Bezier curve at t=z.
Arguments:
- p0 (list): First control point.
- p1 (list): Second control point.
- p2 (list): Third control point.
- z (float): Parameter z, where 0 <= z <= 1.
- n_points (int, optional): Number of points. Defaults to 10.
Returns:
tuple: Two Bezier curves split at t=z.
509def mirror_point(cp: Point, vertex: Point): 510 """Return the mirror of cp about vertex. 511 512 Args: 513 cp (list): Control point to be mirrored. 514 vertex (list): Vertex point. 515 516 Returns: 517 list: Mirrored control point. 518 """ 519 length = distance(cp, vertex) 520 angle = line_angle(cp, vertex) 521 cp2 = line_by_point_angle_length(vertex, angle, length) 522 return cp2
Return the mirror of cp about vertex.
Arguments:
- cp (list): Control point to be mirrored.
- vertex (list): Vertex point.
Returns:
list: Mirrored control point.
525def curve(v1: Point, c1: Point, c2: Point, v2: Point, *args, **kwargs): 526 """Return a cubic Bezier curve/s. 527 528 Args: 529 v1 (list): First vertex. 530 c1 (list): First control point. 531 c2 (list): Second control point. 532 v2 (list): Second vertex. 533 *args: Additional control points and vertices. 534 **kwargs: Additional keyword arguments. 535 536 Returns: 537 list: List of cubic Bezier curves. 538 539 Raises: 540 ValueError: If the number of control points is invalid. 541 """ 542 curves = [Bezier([v1, c1, c2, v2], **kwargs)] 543 last_vertex = v2 544 for arg in args: 545 if len(arg) == 2: 546 c3 = mirror_point(c2, v2) 547 v3 = arg[1] 548 c4 = arg[0] 549 curves.append(Bezier([last_vertex, c3, c4, v3], **kwargs)) 550 last_vertex = v3 551 elif len(arg) == 3: 552 c3 = arg[0] 553 c4 = arg[1] 554 v3 = arg[2] 555 curves.append(Bezier([last_vertex, c3, c4, v3], **kwargs)) 556 last_vertex = v3 557 else: 558 raise ValueError("Invalid number of control points.") 559 560 return curves
Return a cubic Bezier curve/s.
Arguments:
- v1 (list): First vertex.
- c1 (list): First control point.
- c2 (list): Second control point.
- v2 (list): Second vertex.
- *args: Additional control points and vertices.
- **kwargs: Additional keyword arguments.
Returns:
list: List of cubic Bezier curves.
Raises:
- ValueError: If the number of control points is invalid.
563def q_curve(v1: Point, c: Point, v2: Point, *args, **kwargs): 564 """Return a quadratic Bezier curve/s. 565 566 Args: 567 v1 (list): First vertex. 568 c (list): Control point. 569 v2 (list): Second vertex. 570 *args: Additional control points and vertices. 571 **kwargs: Additional keyword arguments. 572 573 Returns: 574 list: List of quadratic Bezier curves. 575 576 Raises: 577 ValueError: If the number of control points is invalid. 578 """ 579 curves = [Bezier([v1, c, v2], **kwargs)] 580 last_vertex = v2 581 for arg in args: 582 if len(arg) == 1: 583 c3 = mirror_point(c, v2) 584 v3 = arg[0] 585 curves.append(Bezier([last_vertex, c3, v3], **kwargs)) 586 last_vertex = v3 587 elif len(arg) == 2: 588 c3 = arg[0] 589 v3 = arg[1] 590 curves.append(Bezier([last_vertex, c3, v3], **kwargs)) 591 last_vertex = v3 592 else: 593 raise ValueError("Invalid number of control points.") 594 595 return curves
Return a quadratic Bezier curve/s.
Arguments:
- v1 (list): First vertex.
- c (list): Control point.
- v2 (list): Second vertex.
- *args: Additional control points and vertices.
- **kwargs: Additional keyword arguments.
Returns:
list: List of quadratic Bezier curves.
Raises:
- ValueError: If the number of control points is invalid.
598def get_quadratic_derivative(t: float, points: Sequence[Point]): 599 """Return the derivative of a quadratic Bezier curve at t. 600 601 Args: 602 t (float): Parameter t, where 0 <= t <= 1. 603 points (list): Control points of the Bezier curve. 604 605 Returns: 606 list: Derivative of the quadratic Bezier curve at t. 607 """ 608 mt = 1 - t 609 d = [ 610 2 * (points[1][0] - points[0][0]), 611 2 * (points[1][1] - points[0][1]), 612 2 * (points[2][0] - points[1][0]), 613 2 * (points[2][1] - points[1][1]), 614 ] 615 616 return [mt * d[0] + t * d[2], mt * d[1] + t * d[3]]
Return the derivative of a quadratic Bezier curve at t.
Arguments:
- t (float): Parameter t, where 0 <= t <= 1.
- points (list): Control points of the Bezier curve.
Returns:
list: Derivative of the quadratic Bezier curve at t.
619def get_cubic_derivative(t: float, points: Sequence[Point]): 620 """Return the derivative of a cubic Bezier curve at t. 621 622 Args: 623 t (float): Parameter t, where 0 <= t <= 1. 624 points (list): Control points of the Bezier curve. 625 626 Returns: 627 list: Derivative of the cubic Bezier curve at t. 628 """ 629 mt = 1 - t 630 a = mt * mt 631 b = 2 * mt * t 632 c = t * t 633 d = [ 634 3 * (points[1][0] - points[0][0]), 635 3 * (points[1][1] - points[0][1]), 636 3 * (points[2][0] - points[1][0]), 637 3 * (points[2][1] - points[1][1]), 638 3 * (points[3][0] - points[2][0]), 639 3 * (points[3][1] - points[2][1]), 640 ] 641 642 return [a * d[0] + b * d[2] + c * d[4], a * d[1] + b * d[3] + c * d[5]]
Return the derivative of a cubic Bezier curve at t.
Arguments:
- t (float): Parameter t, where 0 <= t <= 1.
- points (list): Control points of the Bezier curve.
Returns:
list: Derivative of the cubic Bezier curve at t.
645def get_normal(d: Sequence[float]): 646 """Return the normal of a given line. 647 648 Args: 649 d (list): Derivative of the line. 650 651 Returns: 652 list: Normal of the line. 653 """ 654 q = np.sqrt(d[0] * d[0] + d[1] * d[1]) 655 return [-d[1] / q, d[0] / q]
Return the normal of a given line.
Arguments:
- d (list): Derivative of the line.
Returns:
list: Normal of the line.