1###################################################################### 2# # 3# Copyright 2009 Lucas Heitzmann Gabrielli. # 4# This file is part of gdspy, distributed under the terms of the # 5# Boost Software License - Version 1.0. See the accompanying # 6# LICENSE file or <http://www.boost.org/LICENSE_1_0.txt> # 7# # 8###################################################################### 9 10from __future__ import division 11from __future__ import unicode_literals 12from __future__ import print_function 13from __future__ import absolute_import 14 15import sys 16 17if sys.version_info.major < 3: 18 from builtins import zip 19 from builtins import open 20 from builtins import int 21 from builtins import round 22 from builtins import range 23 from builtins import super 24 25 from future import standard_library 26 27 standard_library.install_aliases() 28else: 29 # Python 3 doesn't have basestring, as unicode is type string 30 # Python 2 doesn't equate unicode to string, but both are basestring 31 # Now isinstance(s, basestring) will be True for any python version 32 basestring = str 33 34import numpy 35from gdspy.path import _func_bezier, _hobby 36 37 38class Curve(object): 39 """ 40 Generation of curves loosely based on SVG paths. 41 42 Short summary of available methods: 43 44 ====== ============================= 45 Method Primitive 46 ====== ============================= 47 L/l Line segments 48 H/h Horizontal line segments 49 V/v Vertical line segments 50 C/c Cubic Bezier curve 51 S/s Smooth cubic Bezier curve 52 Q/q Quadratic Bezier curve 53 T/t Smooth quadratic Bezier curve 54 B/b General degree Bezier curve 55 I/i Smooth interpolating curve 56 arc Elliptical arc 57 ====== ============================= 58 59 The uppercase version of the methods considers that all coordinates 60 are absolute, whereas the lowercase considers that they are relative 61 to the current end point of the curve. 62 63 Parameters 64 ---------- 65 x : number 66 X-coordinate of the starting point of the curve. If this is a 67 complex number, the value of `y` is ignored and the starting 68 point becomes ``(x.real, x.imag)``. 69 y : number 70 Y-coordinate of the starting point of the curve. 71 tolerance : number 72 Tolerance used to calculate a polygonal approximation to the 73 curve. 74 75 Notes 76 ----- 77 78 In all methods of this class that accept coordinate pairs, a single 79 complex number can be passed to be split into its real and imaginary 80 parts. 81 This feature can be useful in expressing coordinates in polar form. 82 83 All commands follow the SVG 2 specification, except for elliptical 84 arcs and smooth interpolating curves, which are inspired by the 85 Metapost syntax. 86 87 Examples 88 -------- 89 >>> curve = gdspy.Curve(3, 4).H(1).q(0.5, 1, 2j).L(2 + 3j, 2, 2) 90 >>> pol = gdspy.Polygon(curve.get_points()) 91 """ 92 93 __slots__ = "points", "tol", "last_c", "last_q" 94 95 def __init__(self, x, y=0, tolerance=0.01): 96 self.last_c = self.last_q = None 97 self.tol = tolerance ** 2 98 if isinstance(x, complex): 99 self.points = [numpy.array((x.real, x.imag))] 100 else: 101 self.points = [numpy.array((x, y))] 102 103 def get_points(self): 104 """ 105 Get the polygonal points that approximate this curve. 106 107 Returns 108 ------- 109 out : Numpy array[N, 2] 110 Vertices of the polygon. 111 """ 112 delta = (self.points[-1] - self.points[0]) ** 2 113 if delta[0] + delta[1] < self.tol: 114 return numpy.array(self.points[:-1]) 115 return numpy.array(self.points) 116 117 def L(self, *xy): 118 """ 119 Add straight line segments to the curve. 120 121 Parameters 122 ---------- 123 xy : numbers 124 Endpoint coordinates of the line segments. 125 126 Returns 127 ------- 128 out : `Curve` 129 This curve. 130 """ 131 self.last_c = self.last_q = None 132 i = 0 133 while i < len(xy): 134 if isinstance(xy[i], complex): 135 self.points.append(numpy.array((xy[i].real, xy[i].imag))) 136 i += 1 137 else: 138 self.points.append(numpy.array((xy[i], xy[i + 1]))) 139 i += 2 140 return self 141 142 def l(self, *xy): 143 """ 144 Add straight line segments to the curve. 145 146 Parameters 147 ---------- 148 xy : numbers 149 Endpoint coordinates of the line segments relative to the 150 current end point. 151 152 Returns 153 ------- 154 out : `Curve` 155 This curve. 156 """ 157 self.last_c = self.last_q = None 158 o = self.points[-1] 159 i = 0 160 while i < len(xy): 161 if isinstance(xy[i], complex): 162 self.points.append(o + numpy.array((xy[i].real, xy[i].imag))) 163 i += 1 164 else: 165 self.points.append(o + numpy.array((xy[i], xy[i + 1]))) 166 i += 2 167 return self 168 169 def H(self, *x): 170 """ 171 Add horizontal line segments to the curve. 172 173 Parameters 174 ---------- 175 x : numbers 176 Endpoint x-coordinates of the line segments. 177 178 Returns 179 ------- 180 out : `Curve` 181 This curve. 182 """ 183 self.last_c = self.last_q = None 184 y0 = self.points[-1][1] 185 self.points.extend(numpy.array((xx, y0)) for xx in x) 186 return self 187 188 def h(self, *x): 189 """ 190 Add horizontal line segments to the curve. 191 192 Parameters 193 ---------- 194 x : numbers 195 Endpoint x-coordinates of the line segments relative to the 196 current end point. 197 198 Returns 199 ------- 200 out : `Curve` 201 This curve. 202 """ 203 self.last_c = self.last_q = None 204 x0, y0 = self.points[-1] 205 self.points.extend(numpy.array((x0 + xx, y0)) for xx in x) 206 return self 207 208 def V(self, *y): 209 """ 210 Add vertical line segments to the curve. 211 212 Parameters 213 ---------- 214 y : numbers 215 Endpoint y-coordinates of the line segments. 216 217 Returns 218 ------- 219 out : `Curve` 220 This curve. 221 """ 222 self.last_c = self.last_q = None 223 x0 = self.points[-1][0] 224 self.points.extend(numpy.array((x0, yy)) for yy in y) 225 return self 226 227 def v(self, *y): 228 """ 229 Add vertical line segments to the curve. 230 231 Parameters 232 ---------- 233 y : numbers 234 Endpoint y-coordinates of the line segments relative to the 235 current end point. 236 237 Returns 238 ------- 239 out : `Curve` 240 This curve. 241 """ 242 self.last_c = self.last_q = None 243 x0, y0 = self.points[-1] 244 self.points.extend(numpy.array((x0, y0 + yy)) for yy in y) 245 return self 246 247 def arc(self, radius, initial_angle, final_angle, rotation=0): 248 """ 249 Add an elliptical arc to the curve. 250 251 Parameters 252 ---------- 253 radius : number, array-like[2] 254 Arc radius. An elliptical arc can be created by passing an 255 array with 2 radii. 256 initial_angle : number 257 Initial angle of the arc (in *radians*). 258 final_angle : number 259 Final angle of the arc (in *radians*). 260 rotation : number 261 Rotation of the axis of the ellipse. 262 263 Returns 264 ------- 265 out : `Curve` 266 This curve. 267 """ 268 self.last_c = self.last_q = None 269 if hasattr(radius, "__iter__"): 270 rx, ry = radius 271 radius = max(radius) 272 else: 273 rx = ry = radius 274 full_angle = abs(final_angle - initial_angle) 275 number_of_points = max( 276 3, 277 1 278 + int(0.5 * full_angle / numpy.arccos(1 - self.tol ** 0.5 / radius) + 0.5), 279 ) 280 angles = numpy.linspace( 281 initial_angle - rotation, final_angle - rotation, number_of_points 282 ) 283 pts = numpy.vstack((rx * numpy.cos(angles), ry * numpy.sin(angles))).T 284 if rotation != 0: 285 rot = numpy.empty_like(pts) 286 c = numpy.cos(rotation) 287 s = numpy.sin(rotation) 288 rot[:, 0] = pts[:, 0] * c - pts[:, 1] * s 289 rot[:, 1] = pts[:, 0] * s + pts[:, 1] * c 290 else: 291 rot = pts 292 pts = rot[1:] - rot[0] + self.points[-1] 293 self.points.extend(xy for xy in pts) 294 return self 295 296 def C(self, *xy): 297 """ 298 Add cubic Bezier curves to the curve. 299 300 Parameters 301 ---------- 302 xy : numbers 303 Coordinate pairs. Each set of 3 pairs are interpreted as 304 the control point at the beginning of the curve, the control 305 point at the end of the curve and the endpoint of the curve. 306 307 Returns 308 ------- 309 out : `Curve` 310 This curve. 311 """ 312 self.last_q = None 313 i = 0 314 while i < len(xy): 315 ctrl = numpy.empty((4, 2)) 316 ctrl[0] = self.points[-1] 317 for j in range(1, 4): 318 if isinstance(xy[i], complex): 319 ctrl[j, 0] = xy[i].real 320 ctrl[j, 1] = xy[i].imag 321 i += 1 322 else: 323 ctrl[j, 0] = xy[i] 324 ctrl[j, 1] = xy[i + 1] 325 i += 2 326 f = _func_bezier(ctrl) 327 uu = [0, 0.2, 0.5, 0.8, 1] 328 fu = [f(u) for u in uu] 329 iu = 1 330 while iu < len(fu): 331 test_u = 0.5 * (uu[iu - 1] + uu[iu]) 332 test_pt = f(test_u) 333 test_err = 0.5 * (fu[iu - 1] + fu[iu]) - test_pt 334 if test_err[0] ** 2 + test_err[1] ** 2 > self.tol: 335 uu.insert(iu, test_u) 336 fu.insert(iu, test_pt) 337 else: 338 iu += 1 339 self.points.extend(xy for xy in fu[1:]) 340 self.last_c = ctrl[2] 341 return self 342 343 def c(self, *xy): 344 """ 345 Add cubic Bezier curves to the curve. 346 347 Parameters 348 ---------- 349 xy : numbers 350 Coordinate pairs. Each set of 3 pairs are interpreted as 351 the control point at the beginning of the curve, the control 352 point at the end of the curve and the endpoint of the curve. 353 All coordinates are relative to the current end point. 354 355 Returns 356 ------- 357 out : `Curve` 358 This curve. 359 """ 360 self.last_q = None 361 x0, y0 = self.points[-1] 362 i = 0 363 while i < len(xy): 364 ctrl = numpy.empty((4, 2)) 365 ctrl[0] = self.points[-1] 366 for j in range(1, 4): 367 if isinstance(xy[i], complex): 368 ctrl[j, 0] = x0 + xy[i].real 369 ctrl[j, 1] = y0 + xy[i].imag 370 i += 1 371 else: 372 ctrl[j, 0] = x0 + xy[i] 373 ctrl[j, 1] = y0 + xy[i + 1] 374 i += 2 375 f = _func_bezier(ctrl) 376 uu = [0, 0.2, 0.5, 0.8, 1] 377 fu = [f(u) for u in uu] 378 iu = 1 379 while iu < len(fu): 380 test_u = 0.5 * (uu[iu - 1] + uu[iu]) 381 test_pt = f(test_u) 382 test_err = 0.5 * (fu[iu - 1] + fu[iu]) - test_pt 383 if test_err[0] ** 2 + test_err[1] ** 2 > self.tol: 384 uu.insert(iu, test_u) 385 fu.insert(iu, test_pt) 386 else: 387 iu += 1 388 self.points.extend(xy for xy in fu[1:]) 389 self.last_c = ctrl[2] 390 return self 391 392 def S(self, *xy): 393 """ 394 Add smooth cubic Bezier curves to the curve. 395 396 Parameters 397 ---------- 398 xy : numbers 399 Coordinate pairs. Each set of 2 pairs are interpreted as 400 the control point at the end of the curve and the endpoint 401 of the curve. The control point at the beginning of the 402 curve is assumed to be the reflection of the control point 403 at the end of the last curve relative to the starting point 404 of the curve. If the previous curve is not a cubic Bezier, 405 the control point is coincident with the starting point. 406 407 Returns 408 ------- 409 out : `Curve` 410 This curve. 411 """ 412 self.last_q = None 413 if self.last_c is None: 414 self.last_c = self.points[-1] 415 i = 0 416 while i < len(xy): 417 ctrl = numpy.empty((4, 2)) 418 ctrl[0] = self.points[-1] 419 ctrl[1] = 2 * ctrl[0] - self.last_c 420 for j in range(2, 4): 421 if isinstance(xy[i], complex): 422 ctrl[j, 0] = xy[i].real 423 ctrl[j, 1] = xy[i].imag 424 i += 1 425 else: 426 ctrl[j, 0] = xy[i] 427 ctrl[j, 1] = xy[i + 1] 428 i += 2 429 f = _func_bezier(ctrl) 430 uu = [0, 0.2, 0.5, 0.8, 1] 431 fu = [f(u) for u in uu] 432 iu = 1 433 while iu < len(fu): 434 test_u = 0.5 * (uu[iu - 1] + uu[iu]) 435 test_pt = f(test_u) 436 test_err = 0.5 * (fu[iu - 1] + fu[iu]) - test_pt 437 if test_err[0] ** 2 + test_err[1] ** 2 > self.tol: 438 uu.insert(iu, test_u) 439 fu.insert(iu, test_pt) 440 else: 441 iu += 1 442 self.points.extend(xy for xy in fu[1:]) 443 self.last_c = ctrl[2] 444 return self 445 446 def s(self, *xy): 447 """ 448 Add smooth cubic Bezier curves to the curve. 449 450 Parameters 451 ---------- 452 xy : numbers 453 Coordinate pairs. Each set of 2 pairs are interpreted as 454 the control point at the end of the curve and the endpoint 455 of the curve. The control point at the beginning of the 456 curve is assumed to be the reflection of the control point 457 at the end of the last curve relative to the starting point 458 of the curve. If the previous curve is not a cubic Bezier, 459 the control point is coincident with the starting point. 460 All coordinates are relative to the current end point. 461 462 Returns 463 ------- 464 out : `Curve` 465 This curve. 466 """ 467 self.last_q = None 468 if self.last_c is None: 469 self.last_c = self.points[-1] 470 x0, y0 = self.points[-1] 471 i = 0 472 while i < len(xy): 473 ctrl = numpy.empty((4, 2)) 474 ctrl[0] = self.points[-1] 475 ctrl[1] = 2 * ctrl[0] - self.last_c 476 for j in range(2, 4): 477 if isinstance(xy[i], complex): 478 ctrl[j, 0] = x0 + xy[i].real 479 ctrl[j, 1] = y0 + xy[i].imag 480 i += 1 481 else: 482 ctrl[j, 0] = x0 + xy[i] 483 ctrl[j, 1] = y0 + xy[i + 1] 484 i += 2 485 f = _func_bezier(ctrl) 486 uu = [0, 0.2, 0.5, 0.8, 1] 487 fu = [f(u) for u in uu] 488 iu = 1 489 while iu < len(fu): 490 test_u = 0.5 * (uu[iu - 1] + uu[iu]) 491 test_pt = f(test_u) 492 test_err = 0.5 * (fu[iu - 1] + fu[iu]) - test_pt 493 if test_err[0] ** 2 + test_err[1] ** 2 > self.tol: 494 uu.insert(iu, test_u) 495 fu.insert(iu, test_pt) 496 else: 497 iu += 1 498 self.points.extend(xy for xy in fu[1:]) 499 self.last_c = ctrl[2] 500 return self 501 502 def Q(self, *xy): 503 """ 504 Add quadratic Bezier curves to the curve. 505 506 Parameters 507 ---------- 508 xy : numbers 509 Coordinate pairs. Each set of 2 pairs are interpreted as 510 the control point and the endpoint of the curve. 511 512 Returns 513 ------- 514 out : `Curve` 515 This curve. 516 """ 517 self.last_c = None 518 i = 0 519 while i < len(xy): 520 ctrl = numpy.empty((3, 2)) 521 ctrl[0] = self.points[-1] 522 for j in range(1, 3): 523 if isinstance(xy[i], complex): 524 ctrl[j, 0] = xy[i].real 525 ctrl[j, 1] = xy[i].imag 526 i += 1 527 else: 528 ctrl[j, 0] = xy[i] 529 ctrl[j, 1] = xy[i + 1] 530 i += 2 531 f = _func_bezier(ctrl) 532 uu = [0, 0.2, 0.5, 0.8, 1] 533 fu = [f(u) for u in uu] 534 iu = 1 535 while iu < len(fu): 536 test_u = 0.5 * (uu[iu - 1] + uu[iu]) 537 test_pt = f(test_u) 538 test_err = 0.5 * (fu[iu - 1] + fu[iu]) - test_pt 539 if test_err[0] ** 2 + test_err[1] ** 2 > self.tol: 540 uu.insert(iu, test_u) 541 fu.insert(iu, test_pt) 542 else: 543 iu += 1 544 self.points.extend(xy for xy in fu[1:]) 545 self.last_q = ctrl[1] 546 return self 547 548 def q(self, *xy): 549 """ 550 Add quadratic Bezier curves to the curve. 551 552 Parameters 553 ---------- 554 xy : numbers 555 Coordinate pairs. Each set of 2 pairs are interpreted as 556 the control point and the endpoint of the curve. 557 All coordinates are relative to the current end point. 558 559 Returns 560 ------- 561 out : `Curve` 562 This curve. 563 """ 564 self.last_c = None 565 x0, y0 = self.points[-1] 566 i = 0 567 while i < len(xy): 568 ctrl = numpy.empty((3, 2)) 569 ctrl[0] = self.points[-1] 570 for j in range(1, 3): 571 if isinstance(xy[i], complex): 572 ctrl[j, 0] = x0 + xy[i].real 573 ctrl[j, 1] = y0 + xy[i].imag 574 i += 1 575 else: 576 ctrl[j, 0] = x0 + xy[i] 577 ctrl[j, 1] = y0 + xy[i + 1] 578 i += 2 579 f = _func_bezier(ctrl) 580 uu = [0, 0.2, 0.5, 0.8, 1] 581 fu = [f(u) for u in uu] 582 iu = 1 583 while iu < len(fu): 584 test_u = 0.5 * (uu[iu - 1] + uu[iu]) 585 test_pt = f(test_u) 586 test_err = 0.5 * (fu[iu - 1] + fu[iu]) - test_pt 587 if test_err[0] ** 2 + test_err[1] ** 2 > self.tol: 588 uu.insert(iu, test_u) 589 fu.insert(iu, test_pt) 590 else: 591 iu += 1 592 self.points.extend(xy for xy in fu[1:]) 593 self.last_q = ctrl[1] 594 return self 595 596 def T(self, *xy): 597 """ 598 Add smooth quadratic Bezier curves to the curve. 599 600 Parameters 601 ---------- 602 xy : numbers 603 Coordinates of the endpoints of the curves. The control 604 point is assumed to be the reflection of the control point 605 of the last curve relative to the starting point of the 606 curve. If the previous curve is not a quadratic Bezier, 607 the control point is coincident with the starting point. 608 609 Returns 610 ------- 611 out : `Curve` 612 This curve. 613 """ 614 self.last_c = None 615 if self.last_q is None: 616 self.last_q = self.points[-1] 617 i = 0 618 while i < len(xy): 619 ctrl = numpy.empty((3, 2)) 620 ctrl[0] = self.points[-1] 621 ctrl[1] = 2 * ctrl[0] - self.last_q 622 if isinstance(xy[i], complex): 623 ctrl[2, 0] = xy[i].real 624 ctrl[2, 1] = xy[i].imag 625 i += 1 626 else: 627 ctrl[2, 0] = xy[i] 628 ctrl[2, 1] = xy[i + 1] 629 i += 2 630 f = _func_bezier(ctrl) 631 uu = [0, 0.2, 0.5, 0.8, 1] 632 fu = [f(u) for u in uu] 633 iu = 1 634 while iu < len(fu): 635 test_u = 0.5 * (uu[iu - 1] + uu[iu]) 636 test_pt = f(test_u) 637 test_err = 0.5 * (fu[iu - 1] + fu[iu]) - test_pt 638 if test_err[0] ** 2 + test_err[1] ** 2 > self.tol: 639 uu.insert(iu, test_u) 640 fu.insert(iu, test_pt) 641 else: 642 iu += 1 643 self.points.extend(xy for xy in fu[1:]) 644 self.last_q = ctrl[1] 645 return self 646 647 def t(self, *xy): 648 """ 649 Add smooth quadratic Bezier curves to the curve. 650 651 Parameters 652 ---------- 653 xy : numbers 654 Coordinates of the endpoints of the curves. The control 655 point is assumed to be the reflection of the control point 656 of the last curve relative to the starting point of the 657 curve. If the previous curve is not a quadratic Bezier, 658 the control point is coincident with the starting point. 659 All coordinates are relative to the current end point. 660 661 Returns 662 ------- 663 out : `Curve` 664 This curve. 665 """ 666 self.last_c = None 667 if self.last_q is None: 668 self.last_q = self.points[-1] 669 x0, y0 = self.points[-1] 670 i = 0 671 while i < len(xy): 672 ctrl = numpy.empty((3, 2)) 673 ctrl[0] = self.points[-1] 674 ctrl[1] = 2 * ctrl[0] - self.last_q 675 if isinstance(xy[i], complex): 676 ctrl[2, 0] = x0 + xy[i].real 677 ctrl[2, 1] = y0 + xy[i].imag 678 i += 1 679 else: 680 ctrl[2, 0] = x0 + xy[i] 681 ctrl[2, 1] = y0 + xy[i + 1] 682 i += 2 683 f = _func_bezier(ctrl) 684 uu = [0, 0.2, 0.5, 0.8, 1] 685 fu = [f(u) for u in uu] 686 iu = 1 687 while iu < len(fu): 688 test_u = 0.5 * (uu[iu - 1] + uu[iu]) 689 test_pt = f(test_u) 690 test_err = 0.5 * (fu[iu - 1] + fu[iu]) - test_pt 691 if test_err[0] ** 2 + test_err[1] ** 2 > self.tol: 692 uu.insert(iu, test_u) 693 fu.insert(iu, test_pt) 694 else: 695 iu += 1 696 self.points.extend(xy for xy in fu[1:]) 697 self.last_q = ctrl[1] 698 return self 699 700 def B(self, *xy): 701 """ 702 Add a general degree Bezier curve. 703 704 Parameters 705 ---------- 706 xy : numbers 707 Coordinate pairs. The last coordinate is the endpoint of 708 curve and all other are control points. 709 710 Returns 711 ------- 712 out : `Curve` 713 This curve. 714 """ 715 self.last_c = self.last_q = None 716 i = 0 717 ctrl = [self.points[-1]] 718 while i < len(xy): 719 if isinstance(xy[i], complex): 720 ctrl.append((xy[i].real, xy[i].imag)) 721 i += 1 722 else: 723 ctrl.append((xy[i], xy[i + 1])) 724 i += 2 725 ctrl = numpy.array(ctrl) 726 f = _func_bezier(ctrl) 727 uu = numpy.linspace(-1, 1, ctrl.shape[0] + 1) 728 uu = list(0.5 * (1 + numpy.sign(uu) * numpy.abs(uu) ** 0.8)) 729 fu = [f(u) for u in uu] 730 iu = 1 731 while iu < len(fu): 732 test_u = 0.5 * (uu[iu - 1] + uu[iu]) 733 test_pt = f(test_u) 734 test_err = 0.5 * (fu[iu - 1] + fu[iu]) - test_pt 735 if test_err[0] ** 2 + test_err[1] ** 2 > self.tol: 736 uu.insert(iu, test_u) 737 fu.insert(iu, test_pt) 738 else: 739 iu += 1 740 self.points.extend(xy for xy in fu[1:]) 741 return self 742 743 def b(self, *xy): 744 """ 745 Add a general degree Bezier curve. 746 747 Parameters 748 ---------- 749 xy : numbers 750 Coordinate pairs. The last coordinate is the endpoint of 751 curve and all other are control points. All coordinates are 752 relative to the current end point. 753 754 Returns 755 ------- 756 out : `Curve` 757 This curve. 758 """ 759 self.last_c = self.last_q = None 760 x0, y0 = self.points[-1] 761 i = 0 762 ctrl = [self.points[-1]] 763 while i < len(xy): 764 if isinstance(xy[i], complex): 765 ctrl.append((x0 + xy[i].real, y0 + xy[i].imag)) 766 i += 1 767 else: 768 ctrl.append((x0 + xy[i], y0 + xy[i + 1])) 769 i += 2 770 ctrl = numpy.array(ctrl) 771 f = _func_bezier(ctrl) 772 uu = numpy.linspace(-1, 1, ctrl.shape[0] + 1) 773 uu = list(0.5 * (1 + numpy.sign(uu) * numpy.abs(uu) ** 0.8)) 774 fu = [f(u) for u in uu] 775 iu = 1 776 while iu < len(fu): 777 test_u = 0.5 * (uu[iu - 1] + uu[iu]) 778 test_pt = f(test_u) 779 test_err = 0.5 * (fu[iu - 1] + fu[iu]) - test_pt 780 if test_err[0] ** 2 + test_err[1] ** 2 > self.tol: 781 uu.insert(iu, test_u) 782 fu.insert(iu, test_pt) 783 else: 784 iu += 1 785 self.points.extend(xy for xy in fu[1:]) 786 return self 787 788 def I( 789 self, 790 points, 791 angles=None, 792 curl_start=1, 793 curl_end=1, 794 t_in=1, 795 t_out=1, 796 cycle=False, 797 ): 798 """ 799 Add a smooth interpolating curve through the given points. 800 801 Uses the Hobby algorithm [1]_ to calculate a smooth 802 interpolating curve made of cubic Bezier segments between each 803 pair of points. 804 805 Parameters 806 ---------- 807 points : array-like[N][2] 808 Vertices in the interpolating curve. 809 angles : array-like[N + 1] or None 810 Tangent angles at each point (in *radians*). Any angles 811 defined as None are automatically calculated. 812 curl_start : number 813 Ratio between the mock curvatures at the first point and at 814 its neighbor. A value of 1 renders the first segment a good 815 approximation for a circular arc. A value of 0 will better 816 approximate a straight segment. It has no effect for closed 817 curves or when an angle is defined for the first point. 818 curl_end : number 819 Ratio between the mock curvatures at the last point and at 820 its neighbor. It has no effect for closed curves or when an 821 angle is defined for the first point. 822 t_in : number or array-like[N + 1] 823 Tension parameter when arriving at each point. One value 824 per point or a single value used for all points. 825 t_out : number or array-like[N + 1] 826 Tension parameter when leaving each point. One value per 827 point or a single value used for all points. 828 cycle : bool 829 If True, calculates control points for a closed curve, 830 with an additional segment connecting the first and last 831 points. 832 833 Returns 834 ------- 835 out : `Curve` 836 This curve. 837 838 Examples 839 -------- 840 >>> c1 = gdspy.Curve(0, 1).I([(1, 1), (2, 1), (1, 0)]) 841 >>> c2 = gdspy.Curve(0, 2).I([(1, 2), (2, 2), (1, 1)], 842 ... cycle=True) 843 >>> ps = gdspy.PolygonSet([c1.get_points(), c2.get_points()]) 844 845 References 846 ---------- 847 .. [1] Hobby, J.D. *Discrete Comput. Geom.* (1986) 1: 123. 848 `DOI: 10.1007/BF02187690 849 <https://doi.org/10.1007/BF02187690>`_ 850 """ 851 pts = numpy.vstack((self.points[-1:], points)) 852 cta, ctb = _hobby(pts, angles, curl_start, curl_end, t_in, t_out, cycle) 853 args = [] 854 args.extend( 855 x 856 for i in range(pts.shape[0] - 1) 857 for x in [ 858 cta[i, 0], 859 cta[i, 1], 860 ctb[i, 0], 861 ctb[i, 1], 862 pts[i + 1, 0], 863 pts[i + 1, 1], 864 ] 865 ) 866 if cycle: 867 args.extend( 868 [cta[-1, 0], cta[-1, 1], ctb[-1, 0], ctb[-1, 1], pts[0, 0], pts[0, 1]] 869 ) 870 return self.C(*args) 871 872 def i( 873 self, 874 points, 875 angles=None, 876 curl_start=1, 877 curl_end=1, 878 t_in=1, 879 t_out=1, 880 cycle=False, 881 ): 882 """ 883 Add a smooth interpolating curve through the given points. 884 885 Uses the Hobby algorithm [1]_ to calculate a smooth 886 interpolating curve made of cubic Bezier segments between each 887 pair of points. 888 889 Parameters 890 ---------- 891 points : array-like[N][2] 892 Vertices in the interpolating curve (relative to the current 893 endpoint). 894 angles : array-like[N + 1] or None 895 Tangent angles at each point (in *radians*). Any angles 896 defined as None are automatically calculated. 897 curl_start : number 898 Ratio between the mock curvatures at the first point and at 899 its neighbor. A value of 1 renders the first segment a good 900 approximation for a circular arc. A value of 0 will better 901 approximate a straight segment. It has no effect for closed 902 curves or when an angle is defined for the first point. 903 curl_end : number 904 Ratio between the mock curvatures at the last point and at 905 its neighbor. It has no effect for closed curves or when an 906 angle is defined for the first point. 907 t_in : number or array-like[N + 1] 908 Tension parameter when arriving at each point. One value 909 per point or a single value used for all points. 910 t_out : number or array-like[N + 1] 911 Tension parameter when leaving each point. One value per 912 point or a single value used for all points. 913 cycle : bool 914 If True, calculates control points for a closed curve, 915 with an additional segment connecting the first and last 916 points. 917 918 Returns 919 ------- 920 out : `Curve` 921 This curve. 922 923 Examples 924 -------- 925 >>> c1 = gdspy.Curve(0, 1).i([(1, 0), (2, 0), (1, -1)]) 926 >>> c2 = gdspy.Curve(0, 2).i([(1, 0), (2, 0), (1, -1)], 927 ... cycle=True) 928 >>> ps = gdspy.PolygonSet([c1.get_points(), c2.get_points()]) 929 930 References 931 ---------- 932 .. [1] Hobby, J.D. *Discrete Comput. Geom.* (1986) 1: 123. 933 `DOI: 10.1007/BF02187690 934 <https://doi.org/10.1007/BF02187690>`_ 935 """ 936 pts = numpy.vstack((numpy.array(((0.0, 0.0),)), points)) + self.points[-1] 937 cta, ctb = _hobby(pts, angles, curl_start, curl_end, t_in, t_out, cycle) 938 args = [] 939 args.extend( 940 x 941 for i in range(pts.shape[0] - 1) 942 for x in [ 943 cta[i, 0], 944 cta[i, 1], 945 ctb[i, 0], 946 ctb[i, 1], 947 pts[i + 1, 0], 948 pts[i + 1, 1], 949 ] 950 ) 951 if cycle: 952 args.extend( 953 [cta[-1, 0], cta[-1, 1], ctb[-1, 0], ctb[-1, 1], pts[0, 0], pts[0, 1]] 954 ) 955 return self.C(*args) 956