geometry.py
1 # SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-or-later OR CERN-OHL-S-2.0+ OR Apache-2.0 2 # type: ignore 3 from itertools import product 4 import unittest 5 from typing import Iterable 6 7 from pdkmaster import _util 8 from pdkmaster.technology import mask as _msk, geometry as _geo 9 10 class GeometryTest(unittest.TestCase): 11 def test_rotation(self): 12 self.assertEqual(_geo.Rotation.No, _geo.Rotation.R0) 13 for name, rot in ( 14 ("no", _geo.Rotation.R0), 15 ("90", _geo.Rotation.R90), 16 ("180", _geo.Rotation.R180), 17 ("270", _geo.Rotation.R270), 18 ("mirrorx", _geo.Rotation.MX), 19 ("mirrorx&90", _geo.Rotation.MX90), 20 ("mirrory", _geo.Rotation.MY), 21 ("mirrory&90", _geo.Rotation.MY90), 22 ): 23 self.assertEqual(_geo.Rotation.from_name(name), rot) 24 25 rot = _geo.Rotation.MX 26 with self.assertRaisesRegex( 27 TypeError, ( 28 "unsupported operand type\(s\) for \*\: " 29 f"'{rot.__class__.__name__}' and 'int'" 30 ) 31 ): 32 rot * 2 33 34 self.assertEqual( 35 _geo.Rotation.R180*_geo.Rotation.R180, 36 _geo.Rotation.R0, 37 ) 38 self.assertEqual( 39 _geo.Rotation.MX90*_geo.Rotation.R90, 40 _geo.Rotation.MY, 41 ) 42 self.assertEqual( 43 _geo.Rotation.MY90*_geo.Rotation.R180, 44 _geo.Rotation.MX90, 45 ) 46 47 r0 = _geo.Rotation.R0 48 49 p = _geo.Point(x=1.0, y=-2.0) 50 rot = _geo.Rotation.MY 51 self.assertEqual(p*r0, p) 52 self.assertEqual(rot*p, p.rotated(rotation=rot)) 53 54 m = _msk.DesignMask(name="mask") 55 r = _geo.Rect.from_size(width=1.0, height=3.0) 56 ms = _geo.MaskShape(mask=m, shape=r) 57 mss = _geo.MaskShapes(ms) 58 rot = _geo.Rotation.R270 59 60 r_r = rot*r 61 r_ms = ms*rot 62 r_mss = rot*mss 63 self.assertEqual(r0*r, r) 64 self.assertEqual(r0*ms, ms) 65 self.assertEqual(mss*r0, mss) 66 self.assertEqual(r_r, r_ms.shape) 67 self.assertEqual(r_ms, r_mss[0]) 68 69 # RotationContext and MoveContext are tested from from the layout 70 # unit tests. 71 72 def test_abstract(self): 73 with self.assertRaisesRegex( 74 TypeError, "^Can't instantiate abstract class _Shape", 75 ): 76 _geo._Shape() 77 with self.assertRaisesRegex( 78 TypeError, "^Can't instantiate abstract class _Rectangular", 79 ): 80 _geo._Rectangular() 81 with self.assertRaisesRegex( 82 TypeError, "^Can't instantiate abstract class _PointsShape", 83 ): 84 _geo._PointsShape() 85 86 def test_pointsshape(self): # Also test _Shape 87 class ShapeTest(_geo._PointsShape): 88 def __init__(self): 89 super().__init__() 90 91 @property 92 def pointsshapes(self) -> Iterable[_geo._PointsShape]: 93 return super().pointsshapes 94 95 @property 96 def bounds(self) -> _geo._Rectangular: 97 return super().bounds 98 99 def moved(self, *, dxy: _geo.Point): 100 return super().moved(dxy=dxy) 101 102 def rotated(self, *, rotation: _geo.Rotation) -> _geo._Shape: 103 return super().rotated(rotation=rotation) 104 105 @property 106 def area(self) -> float: 107 return super().area 108 109 def __eq__(self, o: object) -> bool: 110 return super().__eq__(o) 111 112 @property 113 def points(self) -> Iterable[_geo.Point]: 114 return super().points 115 116 t = ShapeTest() 117 with self.assertRaises(NotImplementedError): 118 t.pointsshapes 119 with self.assertRaises(NotImplementedError): 120 t.bounds 121 with self.assertRaises(NotImplementedError): 122 t.moved(dxy=_geo.origin) 123 with self.assertRaises(NotImplementedError): 124 t.rotated(rotation=_geo.Rotation.R0) 125 with self.assertRaises(NotImplementedError): 126 t.area 127 with self.assertRaises(NotImplementedError): 128 _geo._Shape.__eq__(t, None) 129 with self.assertRaises(NotImplementedError): 130 t.points 131 with self.assertRaises(NotImplementedError): 132 _geo._Shape.__hash__(t) 133 134 with self.assertRaisesRegex( 135 TypeError, 136 f"unsupported operand type\(s\) for \+: " 137 f"'{t.__class__.__name__}' and '{int.__name__}'" 138 ): 139 t + 1 140 with self.assertRaisesRegex( 141 TypeError, 142 f"unsupported operand type\(s\) for \-: " 143 f"'{t.__class__.__name__}' and '{int.__name__}'" 144 ): 145 t - 1 146 147 self.assertNotEqual(t, 1) 148 149 def test_rectangular(self): 150 class RectangularTest(_geo._Rectangular): 151 def __init__(self): 152 super().__init__() 153 154 # _Shape abstract methods 155 @property 156 def pointsshapes(self) -> Iterable[_geo._PointsShape]: 157 return super().pointsshapes 158 @property 159 def bounds(self) -> _geo._Rectangular: 160 return super().bounds 161 def moved(self, *, dxy: _geo.Point): 162 return super().moved(dxy) 163 def rotated(self, *, rotation: _geo.Rotation) -> _geo._Shape: 164 return super().rotated(rotation) 165 @property 166 def area(self) -> float: 167 return super().area 168 def __eq__(self, o: object) -> bool: 169 return super().__eq__(o) 170 171 @property 172 def left(self) -> float: 173 return super().left 174 @property 175 def bottom(self) -> float: 176 return super().bottom 177 @property 178 def right(self) -> float: 179 return super().right 180 @property 181 def top(self) -> float: 182 return super().top 183 184 t = RectangularTest() 185 with self.assertRaises(NotImplementedError): 186 t.left 187 with self.assertRaises(NotImplementedError): 188 t.bottom 189 with self.assertRaises(NotImplementedError): 190 t.right 191 with self.assertRaises(NotImplementedError): 192 t.top 193 with self.assertRaises(NotImplementedError): 194 self.assertNotEqual(t, 1) 195 196 def test_point(self): 197 p = _geo.Point(x=0.0, y=0.0) 198 self.assertTrue((abs(p.x) < _geo.epsilon) and (abs(p.y) < _geo.epsilon)) 199 self.assertEqual(p.area, 0.0) 200 self.assertNotEqual(p, 1) 201 202 p += _geo.Point.from_float(point=(1.0, 2.0)) 203 self.assertEqual(p, _geo.Point(x=1.0, y=2.0)) 204 205 p = _geo.Point.from_point(point=p, x=-1.0) 206 self.assertEqual(p, _geo.Point(x=-1.0, y=2.0)) 207 208 p = _geo.Point.from_point(point=p, y=-p.y) 209 self.assertEqual(p, _geo.Point(x=-1.0, y=-2.0)) 210 211 first = True 212 for p2 in p.pointsshapes: 213 self.assertTrue(first) 214 first = False 215 self.assertEqual(p2, p) 216 first = True 217 for p2 in p.points: 218 self.assertTrue(first) 219 first = False 220 self.assertEqual(p2, p) 221 222 self.assertEqual(p - p, _geo.Point(x=0.0, y=0.0)) 223 with self.assertRaisesRegex( 224 TypeError, 225 "unsupported operand type\(s\) for \+: " 226 f"'{p.__class__.__name__}' and 'str'", 227 ): 228 p + "a" 229 with self.assertRaisesRegex( 230 TypeError, 231 "unsupported operand type\(s\) for \-: " 232 f"'float' and '{p.__class__.__name__}'", 233 ): 234 3.14 - p 235 with self.assertRaisesRegex( 236 TypeError, 237 "unsupported operand type\(s\) for \-: " 238 f"'{p.__class__.__name__}' and 'float'", 239 ): 240 p - 3.14 241 242 self.assertEqual(-2*p, _geo.Point(x=2.0, y=4.0)) 243 with self.assertRaisesRegex( 244 TypeError, 245 f"unsupported operand type\(s\) for \*: " 246 f"'{p.__class__.__name__}' and '{p.__class__.__name__}'", 247 ): 248 p*p 249 250 p2 = p.rotated(rotation=_geo.Rotation.R0) 251 self.assertEqual(p, _geo.Point(x=-1.0, y=-2.0)) 252 self.assertEqual(p, p2) 253 p2 = p.rotated(rotation=_geo.Rotation.R90) 254 self.assertEqual(p, _geo.Point(x=-1.0, y=-2.0)) 255 self.assertEqual(p2, _geo.Point(x=2.0, y=-1.0)) 256 p2 = p.rotated(rotation=_geo.Rotation.R180) 257 self.assertEqual(p, _geo.Point(x=-1.0, y=-2.0)) 258 self.assertEqual(p2, _geo.Point(x=1.0, y=2.0)) 259 p2 = p.rotated(rotation=_geo.Rotation.R270) 260 self.assertEqual(p, _geo.Point(x=-1.0, y=-2.0)) 261 self.assertEqual(p2, _geo.Point(x=-2.0, y=1.0)) 262 p2 = p.rotated(rotation=_geo.Rotation.MX) 263 self.assertEqual(p, _geo.Point(x=-1.0, y=-2.0)) 264 self.assertEqual(p2, _geo.Point(x=-1.0, y=2.0)) 265 p2 = p.rotated(rotation=_geo.Rotation.MX90) 266 self.assertEqual(p, _geo.Point(x=-1.0, y=-2.0)) 267 self.assertEqual(p2, _geo.Point(x=-2.0, y=-1.0)) 268 p2 = p.rotated(rotation=_geo.Rotation.MY) 269 self.assertEqual(p, _geo.Point(x=-1.0, y=-2.0)) 270 self.assertEqual(p2, _geo.Point(x=1.0, y=-2.0)) 271 p2 = p.rotated(rotation=_geo.Rotation.MY90) 272 self.assertEqual(p, _geo.Point(x=-1.0, y=-2.0)) 273 self.assertEqual(p2, _geo.Point(x=2.0, y=1.0)) 274 rot = _geo.Rotation.MY90 275 self.assertEqual(p*rot, rot*p) 276 277 self.assertEqual(str(p), f"({str(p.x)},{str(p.y)})") 278 self.assertEqual(repr(p), f"Point(x={p.x},y={p.y})") 279 280 def test_line(self): 281 p1 = _geo.Point(x=0.0, y=0.0) 282 p2 = _geo.Point(x=1.0, y=-1.0) 283 l = _geo.Line(point1=p1, point2=p2) 284 285 self.assertEqual(l.point1, p1) 286 self.assertEqual(l.point2, p2) 287 self.assertEqual(l.area, 0.0) 288 289 first = True 290 for l2 in l.pointsshapes: 291 self.assertTrue(first) 292 first = False 293 self.assertEqual(l, l2) 294 ps = l.points 295 self.assertEqual(len(ps), 2) 296 self.assertEqual(ps[0], p1) 297 self.assertEqual(ps[1], p2) 298 299 self.assertEqual(l.bounds, l) 300 301 self.assertEqual( 302 l.rotated(rotation=_geo.Rotation.R90), 303 _geo.Line( 304 point1=p1.rotated(rotation=_geo.Rotation.R90), 305 point2=p2.rotated(rotation=_geo.Rotation.R90), 306 ), 307 ) 308 309 dxy = _geo.Point(x=1.0, y=1.0) 310 self.assertEqual( 311 l.moved(dxy=dxy), 312 _geo.Line(point1=p1.moved(dxy=dxy), point2=p2.moved(dxy=dxy)), 313 ) 314 self.assertEqual(-dxy + l, l - dxy) 315 316 self.assertEqual(str(l), f"{p1}-{p2}") 317 self.assertEqual(repr(l), f"Line(point1={p1!r},point2={p2!r})") 318 319 def test_polygon(self): 320 with self.assertRaisesRegex( 321 ValueError, "Last point has to be the same as the first point" 322 ): 323 _geo.Polygon(points=( 324 _geo.Point(x=0.0, y=0.0), _geo.Point(x=0.0, y=1.0), 325 _geo.Point(x=1.0, y=1.0), _geo.Point(x=1.0, y=0.0), 326 )) 327 with self.assertRaisesRegex( 328 ValueError, "Polygon with only colinear points not allowed" 329 ): 330 _geo.Polygon(points=( 331 _geo.Point(x=0.0, y=0.0), _geo.Point(x=0.0, y=1.0), 332 _geo.Point(x=0.0, y=0.5), _geo.Point(x=0.0, y=0.0), 333 )) 334 335 poly1 = _geo.Polygon(points=( 336 _geo.Point(x=0.0, y=0.0), _geo.Point(x=0.0, y=1.0), 337 _geo.Point(x=1.0, y=1.0), _geo.Point(x=1.0, y=0.0), 338 _geo.Point(x=0.0, y=0.0), 339 )) 340 poly2_points = ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)) 341 poly2 = _geo.Polygon.from_floats(points=poly2_points) 342 line1 = _geo.Line( 343 point1=_util.get_first_of(poly1.points), 344 point2=_util.get_nth_of(poly1.points, n=1), 345 ) 346 line2 = _geo.Line( 347 point1=_util.get_first_of(poly1.points), 348 point2=_util.get_nth_of(poly1.points, n=2), 349 ) 350 351 with self.assertRaises(NotImplementedError): 352 poly1.area 353 with self.assertRaisesRegex( 354 TypeError, ( 355 "unsupported operand type\(s\) for \+: " 356 f"'{poly1.__class__.__name__}' and '{poly2.__class__.__name__}'" 357 ) 358 ): 359 poly3 = poly1 + poly2 360 361 self.assertEqual(poly1, poly2) 362 self.assertEqual(str(poly2), f"""{{{ 363 "=".join(str(_geo.Point.from_float(point=p)) for p in poly2_points) 364 }}}""") 365 self.assertEqual(repr(poly2), f"""Polygon(points=({ 366 ",".join(repr(_geo.Point.from_float(point=p)) for p in poly2_points) 367 }))""") 368 self.assertNotEqual(poly1, line1) 369 self.assertNotEqual(poly1, line2) 370 self.assertEqual( 371 poly1.bounds, 372 _geo.Rect(left=0.0, bottom=0.0, right=1.0, top=1.0), 373 ) 374 self.assertNotEqual(line1, poly1) 375 self.assertEqual( 376 poly1.moved(dxy=_geo.Point(x=1.0, y=1.0)), 377 _geo.Polygon.from_floats(points=( 378 (1.0, 1.0), (1.0, 2.0), (2.0, 2.0), (2.0, 1.0), (1.0, 1.0), 379 )) 380 ) 381 self.assertEqual( 382 poly1.rotated(rotation=_geo.Rotation.R90), 383 _geo.Polygon.from_floats(points=( 384 (0.0, 0.0), (-1.0, 0.0), (-1.0, 1.0), (0.0, 1.0), (0.0, 0.0), 385 )) 386 ) 387 388 def test_rect(self): 389 with self.assertRaises(AssertionError): 390 _geo.Rect(left=0.0, bottom=0.0, right=0.0, top=1.0) 391 with self.assertRaises(AssertionError): 392 _geo.Rect.from_size(width=1.0, height=-1.0) 393 394 rect1 = _geo.Rect(left=-1.0, bottom=-1.0, right=1.0, top=1.0) 395 rect2 = _geo.Rect.from_size(width=2.0, height=2.0) 396 rect3 = _geo.Rect.from_corners( 397 corner1=_geo.Point(x=0.0, y=0.0), corner2=_geo.Point(x=2.0, y=2.0), 398 ) 399 rect4 = _geo.Rect.from_floats(values=(0.0, 0.0, 2.0, 2.0)) 400 rect5 = _geo.Rect.from_rect(rect=rect1, bias=1.0) 401 rect6 = _geo.Rect.from_rect(rect=rect3, left=-2.0, bottom=-2.0) 402 rect7 = _geo.Rect.from_float_corners(corners=((-2.0, -2.0), (2.0, 2.0))) 403 404 with self.assertRaisesRegex( 405 RuntimeError, 406 f"Internal error: unsupported rotation 'None'" 407 ): 408 rect1.rotated(rotation=None) 409 410 self.assertEqual( 411 str(rect1), 412 f"[{str(_geo.Point(x=-1.0, y=-1.0))}-{str(_geo.Point(x=1.0, y=1.0))}]", 413 ) 414 self.assertEqual( 415 repr(rect1), "Rect(left=-1.0,bottom=-1.0,right=1.0,top=1.0)", 416 ) 417 self.assertEqual( 418 _util.get_nth_of(rect1.points, n=1), 419 _geo.Point(x=rect1.left, y=rect1.top), 420 ) 421 self.assertEqual(rect1, rect2) 422 self.assertNotEqual(rect1, 1) 423 self.assertEqual(rect1, rect1.rotated(rotation=_geo.Rotation.MX90)) 424 self.assertEqual(round(rect1.area, 6), 4.0) 425 self.assertEqual(rect3, rect4) 426 self.assertEqual(rect1.moved(dxy=_geo.Point(x=1.0, y=1.0)), rect3) 427 self.assertEqual(rect5, rect6) 428 self.assertEqual(rect5, rect7) 429 430 def test_ring(self): 431 rect1 = _geo.Rect(left=0.0, bottom=0.0, right=3.0, top=3.0) 432 rect2 = _geo.Rect(left=0.0, bottom=0.0, right=2.0, top=3.0) 433 rect3 = _geo.Rect(left=0.0, bottom=0.0, right=3.0, top=2.0) 434 435 with self.assertRaises(ValueError): 436 _geo.Ring(outer_bound=rect2, ring_width=1.0) 437 with self.assertRaises(ValueError): 438 _geo.Ring(outer_bound=rect3, ring_width=1.0) 439 440 ring = _geo.Ring(outer_bound=rect1, ring_width=1.0) 441 polygon = _geo.Polygon.from_floats(points=( 442 (0.0, 0.0), 443 (0.0, 3.0), 444 (3.0, 3.0), 445 (3.0, 0.0), 446 (1.0, 0.0), 447 (1.0, 1.0), 448 (2.0, 1.0), 449 (2.0, 2.0), 450 (1.0, 2.0), 451 (1.0, 0.0), 452 (0.0, 0.0), 453 )) 454 self.assertEqual(ring, polygon) 455 456 def test_rectring(self): 457 bb = _geo.Rect.from_size(center=_geo.Point(x=1.0, y=1.5), width=2, height=3) 458 bb2 = _geo.Rect.from_size(center=_geo.Point(x=1.5, y=1.0), width=3, height=2) 459 dxy = _geo.Point(x=1.0, y=1.0) 460 461 with self.assertRaises(ValueError): 462 _geo.RectRing(outer_bound=bb, rect_width=1.0, min_rect_space=1.0) 463 with self.assertRaises(ValueError): 464 _geo.RectRing(outer_bound=bb2, rect_width=1.0, min_rect_space=1.0) 465 466 ring1 = _geo.RectRing(outer_bound=bb, rect_width=0.5, min_rect_space=0.5) 467 ring2 = _geo.RectRing( 468 outer_bound=bb, rect_width=0.5, rect_height=0.5, min_rect_space=0.5, 469 ) 470 ring3 = _geo.RectRing(outer_bound=bb, rect_width=0.5, min_rect_space=1.0) 471 ring4 = _geo.RectRing(outer_bound=bb2, rect_width=0.5, min_rect_space=0.5) 472 ring5 = _geo.RectRing(outer_bound=(bb + dxy), rect_width=0.5, min_rect_space=0.5) 473 ring6 = _geo.RectRing( 474 outer_bound=_geo.Rotation.MX*bb, rect_width=0.5, min_rect_space=0.5, 475 ) 476 477 self.assertEqual(ring1, ring2) 478 self.assertNotEqual(ring1, ring3) 479 self.assertNotEqual(ring1, ring4) 480 481 self.assertNotEqual(ring1, 1.0) 482 483 self.assertEqual(hash(ring1), hash(ring2)) 484 self.assertEqual(repr(ring1), repr(ring2)) 485 self.assertNotEqual(hash(ring1), hash(ring3)) 486 487 self.assertEqual(ring1.bounds, bb) 488 489 self.assertEqual(ring1 + dxy, ring5) 490 self.assertEqual(_geo.Rotation.MX*ring1, ring6) 491 492 rect = _geo.Rect.from_size(width=0.5, height=0.5) 493 self.assertEqual( 494 set(ring1.pointsshapes), 495 set( 496 rect + p for p in ( 497 _geo.Point(x=0.25, y=0.25), 498 _geo.Point(x=0.25, y=1.50), 499 _geo.Point(x=0.25, y=2.75), 500 _geo.Point(x=1.75, y=0.25), 501 _geo.Point(x=1.75, y=1.50), 502 _geo.Point(x=1.75, y=2.75), 503 ) 504 ) 505 ) 506 self.assertEqual( 507 set(ring4.pointsshapes), 508 set( 509 rect + p for p in ( 510 _geo.Point(x=0.25, y=0.25), 511 _geo.Point(x=0.25, y=1.75), 512 _geo.Point(x=1.50, y=0.25), 513 _geo.Point(x=1.50, y=1.75), 514 _geo.Point(x=2.75, y=0.25), 515 _geo.Point(x=2.75, y=1.75), 516 ) 517 ) 518 ) 519 self.assertEqual(ring1.area, 6*rect.area) 520 521 def test_label(self): 522 p = _geo.Point(x=0.0, y=1.0) 523 lbl1 = _geo.Label(origin=_geo.origin, text="lbl1") 524 lbl1bis = _geo.Label(origin=_geo.origin, text="lbl1") 525 lbl2 = _geo.Label(origin=_geo.origin, text="lbl2") 526 lbl3 = _geo.Label(origin=p, text="lbl1") 527 528 self.assertNotEqual(lbl1, _geo.origin) 529 self.assertEqual(lbl1, lbl1bis) 530 self.assertNotEqual(lbl1, lbl2) 531 self.assertNotEqual(lbl1, lbl3) 532 self.assertNotEqual(lbl1, lbl1.moved(dxy=p)) 533 self.assertEqual(lbl1.moved(dxy=p), lbl3) 534 self.assertEqual(lbl1, lbl1.rotated(rotation=_geo.Rotation.MX90)) 535 536 self.assertEqual(hash(lbl1), hash((lbl1.origin, lbl1.text))) 537 538 self.assertEqual(lbl1.pointsshapes, lbl1.origin.pointsshapes) 539 self.assertEqual(lbl1.bounds, lbl1.origin.bounds) 540 self.assertEqual(lbl1.area, lbl1.origin.area) 541 542 def test_multipartshape(self): 543 r_all = _geo.Rect(left=-2.0, bottom=-1.0, right=1.0, top=1.0) 544 r_left = _geo.Rect(left=-2.0, bottom=-1.0, right=0.0, top=1.0) 545 r_right = _geo.Rect(left=0.0, bottom=-1.0, right=1.0, top=1.0) 546 mps = _geo.MultiPartShape(fullshape=r_all, parts=(r_left, r_right)) 547 r2_all = _geo.Rect(left=0.0, bottom=-1.0, right=2.0, top=1.0) 548 r2_left = r_right 549 r2_right = _geo.Rect(left=1.0, bottom=-1.0, right=2.0, top=1.0) 550 mps2 = _geo.MultiPartShape(fullshape=r2_all, parts=(r2_left, r2_right)) 551 552 self.assertEqual(_util.get_first_of(mps.pointsshapes), r_all) 553 self.assertEqual(mps.bounds, r_all) 554 555 part0 = _util.get_first_of(mps.parts) 556 part1 = _util.get_nth_of(mps.parts, n=1) 557 part0_2 = _util.get_first_of(mps2.parts) 558 part1_2 = _util.get_nth_of(mps2.parts, n=1) 559 560 self.assertNotEqual(part0, 3.14) 561 self.assertNotEqual(mps, "") 562 self.assertEqual(part0.partshape, r_left) 563 self.assertEqual(tuple(part0.points), tuple(r_left.points)) 564 self.assertEqual(part0.area, r_left.area) 565 self.assertEqual(part1.multipartshape, mps) 566 self.assertEqual(tuple(part1.pointsshapes), (part1,)) 567 self.assertEqual(part1.bounds, r_right) 568 self.assertEqual(mps.points, mps.fullshape.points) 569 self.assertEqual(mps.area, part0.area + part1.area) 570 self.assertEqual(part1.partshape, part0_2.partshape) 571 self.assertNotEqual(part1, part0_2) 572 573 self.assertEqual(str(part0), f"<<{str(part0.partshape)}>>") 574 self.assertEqual( 575 repr(part0), 576 f"MultiPartShape._Part(partshape={repr(part0.partshape)})", 577 ) 578 s = "|".join(str(p.partshape) for p in mps.parts) 579 self.assertEqual(str(mps), f"({s})") 580 s = repr(mps.fullshape) 581 s2 = ",".join(repr(p.partshape) for p in mps.parts) 582 self.assertEqual(repr(mps), f"MultiPartShape(fullshape={s},parts=({s2}))") 583 584 self.assertEqual({part0, part1}, {part1, part0}) 585 586 p = _geo.Point(x=-2.0, y=3.5) 587 part0_moved = part0 + p 588 self.assertEqual(mps + p, mps.moved(dxy=p)) 589 self.assertEqual(part0_moved.multipartshape, p + mps) 590 591 rot = _geo.Rotation.MX90 592 part1_rotated = rot*part1 593 self.assertEqual(rot * mps, mps.rotated(rotation=rot)) 594 self.assertEqual(part1_rotated.multipartshape, mps*rot) 595 596 def test_multipath_errors(self): 597 # Negative with/distance values 598 with self.assertRaises(ValueError): 599 _geo.Start(point=_geo.origin, width=-1.0) 600 with self.assertRaises(ValueError): 601 _geo.SetWidth(-1.0) 602 with self.assertRaises(ValueError): 603 _geo.GoLeft(-1.0) 604 605 # Only Start 606 with self.assertRaises(ValueError): 607 _geo.MultiPath(_geo.Start(point=_geo.origin, width=1.0)) 608 609 # Start not at start 610 with self.assertRaises(ValueError): 611 _geo.MultiPath( 612 _geo.Start(point=_geo.origin, width=1.0), 613 _geo.Start(point=_geo.origin, width=1.0), 614 ) 615 with self.assertRaises(ValueError): 616 _geo.MultiPath( 617 _geo.Start(point=_geo.origin, width=1.0), 618 _geo.GoLeft(2.0), 619 _geo.Start(point=_geo.origin, width=1.0), 620 ) 621 622 # SetWidth right after Start 623 with self.assertRaises(ValueError): 624 _geo.MultiPath( 625 _geo.Start(point=_geo.origin, width=1.0), 626 _geo.SetWidth(width=2.0), 627 ) 628 629 # SetWidth as last instruction 630 with self.assertRaises(ValueError): 631 _geo.MultiPath( 632 _geo.Start(point=_geo.origin, width=1.0), 633 _geo.GoLeft(2.0), 634 _geo.SetWidth(2.0), 635 ) 636 with self.assertRaises(ValueError): 637 _geo.MultiPath( 638 _geo.Start(point=_geo.origin, width=1.0), 639 _geo.GoLeft(2.0), 640 _geo.SetWidth(2.0), 641 _geo.GoLeft(2.0), 642 _geo.SetWidth(2.0), 643 ) 644 645 # Repeated instruction 646 with self.assertRaises(ValueError): 647 _geo.MultiPath( 648 _geo.Start(point=_geo.origin, width=1.0), 649 _geo.GoLeft(2.0), 650 _geo.GoLeft(2.0), 651 ) 652 653 # Only one direction for Knot 654 with self.assertRaises(TypeError): 655 _geo.Knot(left=(_geo.GoLeft(1.0),)) 656 657 # Instruction after Knot 658 with self.assertRaises(ValueError): 659 _geo.MultiPath( 660 _geo.Start(point=_geo.origin, width=1.0), 661 _geo.GoUp(2.0), 662 _geo.Knot(up=_geo.GoUp(2.0), right=_geo.GoRight(2.0)), 663 _geo.GoUp(2.0), 664 ) 665 666 def test_multipath_compare(self): 667 # Start 668 self.assertEqual( 669 _geo.Start(point=_geo.origin, width=1.0), 670 _geo.Start(point=_geo.origin, width=1.0), 671 ) 672 self.assertNotEqual( 673 _geo.Start(point=_geo.origin, width=1.0), 674 1, 675 ) 676 # SetWidth 677 self.assertEqual( 678 _geo.SetWidth(width=1.0), 679 _geo.SetWidth(width=1.0), 680 ) 681 self.assertNotEqual( 682 _geo.SetWidth(width=1.0), 683 1, 684 ) 685 # _Go 686 self.assertEqual( 687 _geo.GoLeft(1.0), 688 _geo.GoLeft(1.0), 689 ) 690 self.assertNotEqual( 691 _geo.GoLeft(1.0), 692 _geo.GoRight(1.0), 693 ) 694 self.assertNotEqual( 695 _geo.GoLeft(1.0), 696 1, 697 ) 698 699 def test_multipath_properties(self): 700 s = _geo.Start(point=_geo.origin, width=1.0) 701 is_ = ( 702 _geo.GoLeft(1.0), 703 ) 704 mp = _geo.MultiPath(s, *is_) 705 self.assertEqual(s, mp.first) 706 self.assertEqual(is_, mp.instrs) 707 708 def test_multipath_instrs(self): 709 # GoLeft 710 self.assertEqual( 711 _geo.MultiPath( 712 _geo.Start(point=_geo.origin, width=2.0), 713 _geo.GoLeft(1.0), 714 ), 715 _geo.Polygon.from_floats(points=( 716 (0.0, -1.0), 717 (-1.0, -1.0), 718 (-1.0, 1.0), 719 (0.0, 1.0), 720 (0.0, -1.0), 721 )) 722 ) 723 # GoDown 724 self.assertEqual( 725 _geo.MultiPath( 726 _geo.Start(point=_geo.origin, width=2.0), 727 _geo.GoDown(1.0), 728 ), 729 _geo.Polygon.from_floats(points=( 730 (1.0, 0.0), 731 (1.0, -1.0), 732 (-1.0, -1.0), 733 (-1.0, 0.0), 734 (1.0, 0.0), 735 )) 736 ) 737 # GoRight 738 self.assertEqual( 739 _geo.MultiPath( 740 _geo.Start(point=_geo.origin, width=2.0), 741 _geo.GoRight(1.0), 742 ), 743 _geo.Polygon.from_floats(points=( 744 (0.0, 1.0), 745 (1.0, 1.0), 746 (1.0, -1.0), 747 (0.0, -1.0), 748 (0.0, 1.0), 749 )) 750 ) 751 # GoUp 752 self.assertEqual( 753 _geo.MultiPath( 754 _geo.Start(point=_geo.origin, width=2.0), 755 _geo.GoUp(1.0), 756 ), 757 _geo.Polygon.from_floats(points=( 758 (-1.0, 0.0), 759 (-1.0, 1.0), 760 (1.0, 1.0), 761 (1.0, 0.0), 762 (-1.0, 0.0), 763 )) 764 ) 765 766 # GoLeft, SetWidth, GoLeft 767 self.assertEqual( 768 _geo.MultiPath( 769 _geo.Start(point=_geo.origin, width=2.0), 770 _geo.GoLeft(1.0), 771 _geo.SetWidth(4.0), 772 _geo.GoLeft(1.0), 773 ), 774 _geo.Polygon.from_floats(points=( 775 (0.0, -1.0), 776 (-1.0, -1.0), 777 (-1.0, -2.0), 778 (-2.0, -2.0), 779 (-2.0, 2.0), 780 (-1.0, 2.0), 781 (-1.0, 1.0), 782 (0.0, 1.0), 783 (0.0, -1.0), 784 )), 785 ) 786 # GoLeft, GoDown 787 self.assertEqual( 788 _geo.MultiPath( 789 _geo.Start(point=_geo.origin, width=2.0), 790 _geo.GoLeft(2.0), 791 _geo.GoDown(2.0), 792 ), 793 _geo.Polygon.from_floats(points=( 794 (0.0, -1.0), 795 (-1.0, -1.0), 796 (-1.0, -2.0), 797 (-3.0, -2.0), 798 (-3.0, 1.0), 799 (0.0, 1.0), 800 (0.0, -1.0), 801 )), 802 ) 803 # GoLeft, SetWidth, GoDown 804 self.assertEqual( 805 _geo.MultiPath( 806 _geo.Start(point=_geo.origin, width=2.0), 807 _geo.GoLeft(3.0), 808 _geo.SetWidth(4.0), 809 _geo.GoDown(2.0), 810 ), 811 _geo.Polygon.from_floats(points=( 812 (0.0, -1.0), 813 (-1.0, -1.0), 814 (-1.0, -2.0), 815 (-5.0, -2.0), 816 (-5.0, 1.0), 817 (0.0, 1.0), 818 (0.0, -1.0), 819 )), 820 ) 821 # GoLeft, GoRight 822 with self.assertRaises(ValueError): 823 _geo.MultiPath( 824 _geo.Start(point=_geo.origin, width=2.0), 825 _geo.GoLeft(2.0), 826 _geo.GoRight(2.0), 827 ), 828 # GoLeft, GoUp 829 self.assertEqual( 830 _geo.MultiPath( 831 _geo.Start(point=_geo.origin, width=2.0), 832 _geo.GoLeft(2.0), 833 _geo.GoUp(2.0), 834 ), 835 _geo.Polygon.from_floats(points=( 836 (0.0, -1.0), 837 (-3.0, -1.0), 838 (-3.0, 2.0), 839 (-1.0, 2.0), 840 (-1.0, 1.0), 841 (0.0, 1.0), 842 (0.0, -1.0), 843 )), 844 ) 845 # GoLeft, SetWidth, GoUp 846 self.assertEqual( 847 _geo.MultiPath( 848 _geo.Start(point=_geo.origin, width=2.0), 849 _geo.GoLeft(3.0), 850 _geo.SetWidth(4.0), 851 _geo.GoUp(2.0), 852 ), 853 _geo.Polygon.from_floats(points=( 854 (0.0, -1.0), 855 (-5.0, -1.0), 856 (-5.0, 2.0), 857 (-1.0, 2.0), 858 (-1.0, 1.0), 859 (0.0, 1.0), 860 (0.0, -1.0), 861 )), 862 ) 863 864 # GoDown, GoLeft 865 self.assertEqual( 866 _geo.MultiPath( 867 _geo.Start(point=_geo.origin, width=2.0), 868 _geo.GoDown(2.0), 869 _geo.GoLeft(2.0), 870 ), 871 _geo.Polygon.from_floats(points=( 872 (1.0, 0.0), 873 (1.0, -3.0), 874 (-2.0, -3.0), 875 (-2.0, -1.0), 876 (-1.0, -1.0), 877 (-1.0, 0.0), 878 (1.0, 0.0), 879 )), 880 ) 881 # GoDown, SetWidth, GoLeft 882 self.assertEqual( 883 _geo.MultiPath( 884 _geo.Start(point=_geo.origin, width=2.0), 885 _geo.GoDown(3.0), 886 _geo.SetWidth(4.0), 887 _geo.GoLeft(2.0), 888 ), 889 _geo.Polygon.from_floats(points=( 890 (1.0, 0.0), 891 (1.0, -5.0), 892 (-2.0, -5.0), 893 (-2.0, -1.0), 894 (-1.0, -1.0), 895 (-1.0, 0.0), 896 (1.0, 0.0), 897 )), 898 ) 899 # GoDown, SetWidth, GoDown 900 self.assertEqual( 901 _geo.MultiPath( 902 _geo.Start(point=_geo.origin, width=2.0), 903 _geo.GoDown(1.0), 904 _geo.SetWidth(4.0), 905 _geo.GoDown(1.0), 906 ), 907 _geo.Polygon.from_floats(points=( 908 (1.0, 0.0), 909 (1.0, -1.0), 910 (2.0, -1.0), 911 (2.0, -2.0), 912 (-2.0, -2.0), 913 (-2.0, -1.0), 914 (-1.0, -1.0), 915 (-1.0, 0.0), 916 (1.0, 0.0), 917 )), 918 ) 919 # GoDown, GoRight 920 self.assertEqual( 921 _geo.MultiPath( 922 _geo.Start(point=_geo.origin, width=2.0), 923 _geo.GoDown(2.0), 924 _geo.GoRight(2.0), 925 ), 926 _geo.Polygon.from_floats(points=( 927 (1.0, 0.0), 928 (1.0, -1.0), 929 (2.0, -1.0), 930 (2.0, -3.0), 931 (-1.0, -3.0), 932 (-1.0, 0.0), 933 (1.0, 0.0), 934 )), 935 ) 936 # GoDown, SetWidth, GoRight 937 self.assertEqual( 938 _geo.MultiPath( 939 _geo.Start(point=_geo.origin, width=2.0), 940 _geo.GoDown(3.0), 941 _geo.SetWidth(4.0), 942 _geo.GoRight(2.0), 943 ), 944 _geo.Polygon.from_floats(points=( 945 (1.0, 0.0), 946 (1.0, -1.0), 947 (2.0, -1.0), 948 (2.0, -5.0), 949 (-1.0, -5.0), 950 (-1.0, 0.0), 951 (1.0, 0.0), 952 )), 953 ) 954 # GoDown, GoUp 955 with self.assertRaises(ValueError): 956 _geo.MultiPath( 957 _geo.Start(point=_geo.origin, width=2.0), 958 _geo.GoDown(2.0), 959 _geo.GoUp(2.0), 960 ), 961 962 # GoRight, GoLeft 963 with self.assertRaises(ValueError): 964 _geo.MultiPath( 965 _geo.Start(point=_geo.origin, width=2.0), 966 _geo.GoRight(2.0), 967 _geo.GoLeft(2.0), 968 ), 969 # GoRight, GoDown 970 self.assertEqual( 971 _geo.MultiPath( 972 _geo.Start(point=_geo.origin, width=2.0), 973 _geo.GoRight(2.0), 974 _geo.GoDown(2.0), 975 ), 976 _geo.Polygon.from_floats(points=( 977 (0.0, 1.0), 978 (3.0, 1.0), 979 (3.0, -2.0), 980 (1.0, -2.0), 981 (1.0, -1.0), 982 (0.0, -1.0), 983 (0.0, 1.0), 984 )), 985 ) 986 # GoRight, SetWidth, GoDown 987 self.assertEqual( 988 _geo.MultiPath( 989 _geo.Start(point=_geo.origin, width=2.0), 990 _geo.GoRight(3.0), 991 _geo.SetWidth(4.0), 992 _geo.GoDown(2.0), 993 ), 994 _geo.Polygon.from_floats(points=( 995 (0.0, 1.0), 996 (5.0, 1.0), 997 (5.0, -2.0), 998 (1.0, -2.0), 999 (1.0, -1.0), 1000 (0.0, -1.0), 1001 (0.0, 1.0), 1002 )), 1003 ) 1004 # GoRight, SetWidth, GoRight 1005 self.assertEqual( 1006 _geo.MultiPath( 1007 _geo.Start(point=_geo.origin, width=2.0), 1008 _geo.GoRight(1.0), 1009 _geo.SetWidth(4.0), 1010 _geo.GoRight(1.0), 1011 ), 1012 _geo.Polygon.from_floats(points=( 1013 (0.0, 1.0), 1014 (1.0, 1.0), 1015 (1.0, 2.0), 1016 (2.0, 2.0), 1017 (2.0, -2.0), 1018 (1.0, -2.0), 1019 (1.0, -1.0), 1020 (0.0, -1.0), 1021 (0.0, 1.0), 1022 )), 1023 ) 1024 # GoRight, GoUp 1025 self.assertEqual( 1026 _geo.MultiPath( 1027 _geo.Start(point=_geo.origin, width=2.0), 1028 _geo.GoRight(2.0), 1029 _geo.GoUp(2.0), 1030 ), 1031 _geo.Polygon.from_floats(points=( 1032 (0.0, 1.0), 1033 (1.0, 1.0), 1034 (1.0, 2.0), 1035 (3.0, 2.0), 1036 (3.0, -1.0), 1037 (0.0, -1.0), 1038 (0.0, 1.0), 1039 )), 1040 ) 1041 # GoRight, SetWidth, GoUp 1042 self.assertEqual( 1043 _geo.MultiPath( 1044 _geo.Start(point=_geo.origin, width=2.0), 1045 _geo.GoRight(3.0), 1046 _geo.SetWidth(4.0), 1047 _geo.GoUp(2.0), 1048 ), 1049 _geo.Polygon.from_floats(points=( 1050 (0.0, 1.0), 1051 (1.0, 1.0), 1052 (1.0, 2.0), 1053 (5.0, 2.0), 1054 (5.0, -1.0), 1055 (0.0, -1.0), 1056 (0.0, 1.0), 1057 )), 1058 ) 1059 1060 # GoUp, GoLeft 1061 self.assertEqual( 1062 _geo.MultiPath( 1063 _geo.Start(point=_geo.origin, width=2.0), 1064 _geo.GoUp(2.0), 1065 _geo.GoLeft(2.0), 1066 ), 1067 _geo.Polygon.from_floats(points=( 1068 (-1.0, 0.0), 1069 (-1.0, 1.0), 1070 (-2.0, 1.0), 1071 (-2.0, 3.0), 1072 (1.0, 3.0), 1073 (1.0, 0.0), 1074 (-1.0, 0.0), 1075 )), 1076 ) 1077 # GoUp, SetWidth, GoLeft 1078 self.assertEqual( 1079 _geo.MultiPath( 1080 _geo.Start(point=_geo.origin, width=2.0), 1081 _geo.GoUp(3.0), 1082 _geo.SetWidth(4.0), 1083 _geo.GoLeft(2.0), 1084 ), 1085 _geo.Polygon.from_floats(points=( 1086 (-1.0, 0.0), 1087 (-1.0, 1.0), 1088 (-2.0, 1.0), 1089 (-2.0, 5.0), 1090 (1.0, 5.0), 1091 (1.0, 0.0), 1092 (-1.0, 0.0), 1093 )), 1094 ) 1095 # GoUp, GoDown 1096 with self.assertRaises(ValueError): 1097 _geo.MultiPath( 1098 _geo.Start(point=_geo.origin, width=2.0), 1099 _geo.GoUp(2.0), 1100 _geo.GoDown(2.0), 1101 ), 1102 # GoUp, GoRight 1103 self.assertEqual( 1104 _geo.MultiPath( 1105 _geo.Start(point=_geo.origin, width=2.0), 1106 _geo.GoUp(2.0), 1107 _geo.GoRight(2.0), 1108 ), 1109 _geo.Polygon.from_floats(points=( 1110 (-1.0, 0.0), 1111 (-1.0, 3.0), 1112 (2.0, 3.0), 1113 (2.0, 1.0), 1114 (1.0, 1.0), 1115 (1.0, 0.0), 1116 (-1.0, 0.0), 1117 )), 1118 ) 1119 # GoUp, SetWidth, GoRight 1120 self.assertEqual( 1121 _geo.MultiPath( 1122 _geo.Start(point=_geo.origin, width=2.0), 1123 _geo.GoUp(3.0), 1124 _geo.SetWidth(4.0), 1125 _geo.GoRight(2.0), 1126 ), 1127 _geo.Polygon.from_floats(points=( 1128 (-1.0, 0.0), 1129 (-1.0, 5.0), 1130 (2.0, 5.0), 1131 (2.0, 1.0), 1132 (1.0, 1.0), 1133 (1.0, 0.0), 1134 (-1.0, 0.0), 1135 )), 1136 ) 1137 # GoUp, SetWidth, GoUp 1138 self.assertEqual( 1139 _geo.MultiPath( 1140 _geo.Start(point=_geo.origin, width=2.0), 1141 _geo.GoUp(1.0), 1142 _geo.SetWidth(4.0), 1143 _geo.GoUp(1.0), 1144 ), 1145 _geo.Polygon.from_floats(points=( 1146 (-1.0, 0.0), 1147 (-1.0, 1.0), 1148 (-2.0, 1.0), 1149 (-2.0, 2.0), 1150 (2.0, 2.0), 1151 (2.0, 1.0), 1152 (1.0, 1.0), 1153 (1.0, 0.0), 1154 (-1.0, 0.0), 1155 )), 1156 ) 1157 1158 def test_multipath_knot(self): 1159 # Different Knot implementations covering different corner cases. 1160 # A set of shapes for each previous _Go direction 1161 1162 ### After GoUp 1163 1164 self.assertEqual( 1165 _geo.MultiPath( 1166 _geo.Start(point=_geo.origin, width=2.0), 1167 _geo.GoUp(5.0), 1168 _geo.Knot( 1169 left=( 1170 _geo.SetWidth(3.0), 1171 _geo.GoLeft(2.0), 1172 ), 1173 right=( 1174 _geo.SetWidth(3.0), 1175 _geo.GoRight(2.0), 1176 ), 1177 ) 1178 ), 1179 _geo.Polygon.from_floats(points=( 1180 (-1.0, 0.0), 1181 (-1.0, 3.5), 1182 (-2.0, 3.5), 1183 (-2.0, 6.5), 1184 (2.0, 6.5), 1185 (2.0, 3.5), 1186 (1.0, 3.5), 1187 (1.0, 0.0), 1188 (-1.0, 0.0), 1189 )), 1190 ) 1191 1192 self.assertEqual( 1193 _geo.MultiPath( 1194 _geo.Start(point=_geo.origin, width=2.0), 1195 _geo.GoUp(5.0), 1196 _geo.Knot( 1197 left=( 1198 _geo.SetWidth(3.0), 1199 _geo.GoLeft(2.0), 1200 ), 1201 right=( 1202 _geo.SetWidth(4.0), 1203 _geo.GoRight(2.0), 1204 ), 1205 ) 1206 ), 1207 _geo.Polygon.from_floats(points=( 1208 (-1.0, 0.0), 1209 (-1.0, 3.5), 1210 (-2.0, 3.5), 1211 (-2.0, 6.5), 1212 (0.0, 6.5), 1213 (0.0, 7.0), 1214 (2.0, 7.0), 1215 (2.0, 3.0), 1216 (1.0, 3.0), 1217 (1.0, 0.0), 1218 (-1.0, 0.0), 1219 )), 1220 ) 1221 1222 self.assertEqual( 1223 _geo.MultiPath( 1224 _geo.Start(point=_geo.origin, width=2.0), 1225 _geo.GoUp(5.0), 1226 _geo.Knot( 1227 left=( 1228 _geo.SetWidth(3.0), 1229 _geo.GoLeft(2.0), 1230 ), 1231 up=_geo.GoUp(5.0), 1232 right=( 1233 _geo.SetWidth(3.0), 1234 _geo.GoRight(2.0), 1235 ), 1236 ) 1237 ), 1238 _geo.Polygon.from_floats(points=( 1239 (-1.0, 0.0), 1240 (-1.0, 3.5), 1241 (-2.0, 3.5), 1242 (-2.0, 6.5), 1243 (-1.0, 6.5), 1244 (-1.0, 10.0), 1245 (1.0, 10.0), 1246 (1.0, 6.5), 1247 (2.0, 6.5), 1248 (2.0, 3.5), 1249 (1.0, 3.5), 1250 (1.0, 0.0), 1251 (-1.0, 0.0), 1252 )), 1253 ) 1254 1255 self.assertEqual( 1256 _geo.MultiPath( 1257 _geo.Start(point=_geo.origin, width=2.0), 1258 _geo.GoUp(5.0), 1259 _geo.Knot( 1260 left=( 1261 _geo.SetWidth(3.0), 1262 _geo.GoLeft(3.0), 1263 ), 1264 up=_geo.GoUp(1.0), 1265 right=( 1266 _geo.SetWidth(3.0), 1267 _geo.GoRight(3.0), 1268 ), 1269 ) 1270 ), 1271 _geo.Polygon.from_floats(points=( 1272 (-1.0, 0.0), 1273 (-1.0, 3.5), 1274 (-3.0, 3.5), 1275 (-3.0, 6.5), 1276 (-1.0, 6.5), 1277 (-1.0, 6.0), 1278 (1.0, 6.0), 1279 (1.0, 6.5), 1280 (3.0, 6.5), 1281 (3.0, 3.5), 1282 (1.0, 3.5), 1283 (1.0, 0.0), 1284 (-1.0, 0.0), 1285 )), 1286 ) 1287 1288 self.assertEqual( 1289 _geo.MultiPath( 1290 _geo.Start(point=_geo.origin, width=2.0), 1291 _geo.GoUp(5.0), 1292 _geo.Knot( 1293 up=( 1294 _geo.GoUp(5.0), 1295 ), 1296 right=( 1297 _geo.SetWidth(3.0), 1298 _geo.GoRight(3.0), 1299 ), 1300 ) 1301 ), 1302 _geo.Polygon.from_floats(points=( 1303 (-1.0, 0.0), 1304 (-1.0, 10.0), 1305 (1.0, 10.0), 1306 (1.0, 6.5), 1307 (3.0, 6.5), 1308 (3.0, 3.5), 1309 (1.0, 3.5), 1310 (1.0, 0.0), 1311 (-1.0, 0.0), 1312 )), 1313 ) 1314 1315 self.assertEqual( 1316 _geo.MultiPath( 1317 _geo.Start(point=_geo.origin, width=2.0), 1318 _geo.GoUp(5.0), 1319 _geo.Knot( 1320 up=( 1321 _geo.SetWidth(1.0), 1322 _geo.GoUp(5.0), 1323 ), 1324 right=( 1325 _geo.SetWidth(3.0), 1326 _geo.GoRight(3.0), 1327 ), 1328 ) 1329 ), 1330 _geo.Polygon.from_floats(points=( 1331 (-1.0, 0.0), 1332 (-1.0, 5.0), 1333 (-0.5, 5.0), 1334 (-0.5, 10.0), 1335 (0.5, 10.0), 1336 (0.5, 6.5), 1337 (3.0, 6.5), 1338 (3.0, 3.5), 1339 (1.0, 3.5), 1340 (1.0, 0.0), 1341 (-1.0, 0.0), 1342 )), 1343 ) 1344 1345 ### After GoLeft 1346 1347 self.assertEqual( 1348 _geo.MultiPath( 1349 _geo.Start(point=_geo.origin, width=2.0), 1350 _geo.GoLeft(5.0), 1351 _geo.Knot( 1352 down=( 1353 _geo.SetWidth(3.0), 1354 _geo.GoDown(2.0), 1355 ), 1356 up=( 1357 _geo.SetWidth(3.0), 1358 _geo.GoUp(2.0), 1359 ), 1360 ) 1361 ), 1362 _geo.Polygon.from_floats(points=( 1363 (0.0, -1.0), 1364 (-3.5, -1.0), 1365 (-3.5, -2.0), 1366 (-6.5, -2.0), 1367 (-6.5, 2.0), 1368 (-3.5, 2.0), 1369 (-3.5, 1.0), 1370 (0.0, 1.0), 1371 (0.0, -1.0), 1372 )), 1373 ) 1374 1375 ### After GoDown 1376 1377 self.assertEqual( 1378 _geo.MultiPath( 1379 _geo.Start(point=_geo.origin, width=2.0), 1380 _geo.GoDown(5.0), 1381 _geo.Knot( 1382 left=( 1383 _geo.SetWidth(3.0), 1384 _geo.GoLeft(2.0), 1385 ), 1386 right=( 1387 _geo.SetWidth(3.0), 1388 _geo.GoRight(2.0), 1389 ), 1390 ) 1391 ), 1392 _geo.Polygon.from_floats(points=( 1393 (1.0, 0.0), 1394 (1.0, -3.5), 1395 (2.0, -3.5), 1396 (2.0, -6.5), 1397 (-2.0, -6.5), 1398 (-2.0, -3.5), 1399 (-1.0, -3.5), 1400 (-1.0, 0.0), 1401 (1.0, 0.0), 1402 )), 1403 ) 1404 1405 ### After GoRight 1406 1407 self.assertEqual( 1408 _geo.MultiPath( 1409 _geo.Start(point=_geo.origin, width=2.0), 1410 _geo.GoRight(5.0), 1411 _geo.Knot( 1412 down=( 1413 _geo.SetWidth(3.0), 1414 _geo.GoDown(2.0), 1415 ), 1416 up=( 1417 _geo.SetWidth(3.0), 1418 _geo.GoUp(2.0), 1419 ), 1420 ) 1421 ), 1422 _geo.Polygon.from_floats(points=( 1423 (0.0, 1.0), 1424 (3.5, 1.0), 1425 (3.5, 2.0), 1426 (6.5, 2.0), 1427 (6.5, -2.0), 1428 (3.5, -2.0), 1429 (3.5, -1.0), 1430 (0.0, -1.0), 1431 (0.0, 1.0), 1432 )), 1433 ) 1434 1435 # Two nested Knot instructions 1436 1437 self.assertEqual( 1438 _geo.MultiPath( 1439 _geo.Start(point=_geo.origin, width=2.0), 1440 _geo.GoUp(5.0), 1441 _geo.Knot( 1442 left=( 1443 _geo.GoLeft(5.0), 1444 _geo.Knot( 1445 down=_geo.GoDown(5.0), 1446 left=_geo.GoLeft(5.0), 1447 up=_geo.GoUp(5.0), 1448 ) 1449 ), 1450 up=_geo.GoUp(5.0), 1451 right=_geo.GoRight(5.0), 1452 ) 1453 ), 1454 _geo.Polygon.from_floats(points=( 1455 (-1.0, 0.0), 1456 (-1.0, 4.0), 1457 (-4.0, 4.0), 1458 (-4.0, 0.0), 1459 (-6.0, 0.0), 1460 (-6.0, 4.0), 1461 (-10.0, 4.0), 1462 (-10.0, 6.0), 1463 (-6.0, 6.0), 1464 (-6.0, 10.0), 1465 (-4.0, 10.0), 1466 (-4.0, 6.0), 1467 (-1.0, 6.0), 1468 (-1.0, 10.0), 1469 (1.0, 10.0), 1470 (1.0, 6.0), 1471 (5.0, 6.0), 1472 (5.0, 4.0), 1473 (1.0, 4.0), 1474 (1.0, 0.0), 1475 (-1.0, 0.0), 1476 )), 1477 ) 1478 1479 def test_multishape(self): 1480 p = _geo.Point(x=1.0, y=-1.0) 1481 p2 = _geo.Point(x=1.0, y=1.0) 1482 l = _geo.Line(point1=_geo.Point(x=0.0, y=0.0), point2=_geo.Point(x=1.0, y=1.0)) 1483 r = _geo.Rect(left=-2.0, bottom=-3.0, right=2.0, top=-2.0) 1484 1485 with self.assertRaisesRegex( 1486 ValueError, "MultiShape has to consist of more than one shape", 1487 ): 1488 _geo.MultiShape(shapes=(p,)) 1489 1490 ms1 = _geo.MultiShape(shapes=(p, l, r)) 1491 ms2 = _geo.MultiShape(shapes=(l, r, p)) 1492 ms3 = _geo.MultiShape(shapes=(l, _geo.MultiShape(shapes=(r, p)))) 1493 ms4 = _geo.MultiShape(shapes=(p, l)) 1494 ms5 = _geo.MultiShape(shapes=(p, p2)) 1495 1496 self.assertNotEqual(ms1, "") 1497 self.assertEqual(ms1, ms2) 1498 self.assertEqual(ms1, ms3) 1499 self.assertEqual(hash(ms1), hash(ms2)) 1500 self.assertEqual(len(ms1), 3) 1501 self.assertTrue(l in ms2) 1502 self.assertAlmostEqual(ms1.area, 4.0, 6) 1503 self.assertNotEqual(ms1, ms4) 1504 self.assertEqual(set(ms1), {p, l, r}) 1505 self.assertEqual( 1506 ms1.moved(dxy=p), 1507 _geo.MultiShape(shapes=(r + p, l + p, 2*p)), 1508 ) 1509 rot = _geo.Rotation.MY 1510 self.assertEqual( 1511 ms1.rotated(rotation=rot), 1512 _geo.MultiShape(shapes=( 1513 r.rotated(rotation=rot), l.rotated(rotation=rot), 1514 p.rotated(rotation=rot), 1515 )), 1516 ) 1517 self.assertEqual( 1518 ms1.bounds, 1519 _geo.Rect(left=-2.0, bottom=-3.0, right=2.0, top=1.0), 1520 ) 1521 self.assertEqual(ms5.bounds, _geo.Line(point1=p, point2=p2)) 1522 self.assertEqual(str(ms1), f"({str(l)},{str(p)},{str(r)})") 1523 self.assertEqual( 1524 repr(ms1), 1525 "MultiShape(shapes=(Line(point1=Point(x=0.0,y=0.0),point2=Point(x=1.0,y=1.0)),Point(x=1.0,y=-1.0),Rect(left=-2.0,bottom=-3.0,right=2.0,top=-2.0)))" 1526 ) 1527 1528 def test_repeatedshape(self): 1529 s = _geo.Rect.from_size(width=2.0, height=2.0) 1530 dxy1 = _geo.Point(x=5.0, y=0.0) 1531 dxy2 = _geo.Point(x=0.0, y=5.0) 1532 p = _geo.Point(x=0.0, y=1.0) 1533 1534 with self.assertRaisesRegex( 1535 ValueError, "n has to be equal to or higher than 2, not '1'" 1536 ): 1537 _geo.RepeatedShape(shape=s, offset0=_geo.origin, n=1, n_dxy=dxy1) 1538 with self.assertRaisesRegex( 1539 ValueError, "m has to be equal to or higher than 1, not '0'" 1540 ): 1541 _geo.RepeatedShape(shape=s, offset0=_geo.origin, n=2, n_dxy=dxy1, m=0) 1542 with self.assertRaisesRegex( 1543 ValueError, "m_dxy may not be None if m > 1" 1544 ): 1545 _geo.RepeatedShape(shape=s, offset0=_geo.origin, n=2, n_dxy=dxy1, m=2) 1546 1547 rp1 = _geo.RepeatedShape( 1548 shape=s, offset0=_geo.origin, n=2, n_dxy=dxy1, 1549 ) 1550 rp2 = s.repeat( 1551 offset0=_geo.origin, n=2, n_dxy=dxy1, 1552 ) 1553 rp3 = _geo.RepeatedShape( 1554 shape=s, offset0=p, n=2, n_dxy=dxy1, 1555 ) 1556 rp4 = _geo.RepeatedShape( 1557 shape=s, offset0=_geo.origin, n=2, n_dxy=dxy1, m=2, m_dxy=dxy2, 1558 ) 1559 rp5 = _geo.RepeatedShape( 1560 shape=s, offset0=_geo.origin, n=2, n_dxy=dxy2, m=2, m_dxy=dxy1, 1561 ) 1562 rp6 = _geo.RepeatedShape( 1563 shape=s, offset0=_geo.origin, n=2, n_dxy=dxy1, m=3, m_dxy=dxy2, 1564 ) 1565 rp7 = _geo.RepeatedShape( 1566 shape=s, offset0=_geo.origin, n=3, n_dxy=dxy2, m=2, m_dxy=dxy1, 1567 ) 1568 rp8 = _geo.RepeatedShape( 1569 shape=s, offset0=_geo.origin, n=2, n_dxy=dxy2, m=3, m_dxy=dxy1, 1570 ) 1571 1572 self.assertAlmostEqual(rp1.area, 2*s.area, 6) 1573 self.assertNotEqual(rp1, False) 1574 self.assertEqual(rp1, rp2) 1575 self.assertEqual(hash(rp1), hash(rp2)) 1576 self.assertNotEqual(rp1, rp3) 1577 self.assertEqual(rp1.moved(dxy=p), rp3) 1578 self.assertNotEqual(rp1, rp4) 1579 self.assertEqual(rp4, rp5) 1580 self.assertEqual(rp6, rp7) 1581 self.assertEqual(hash(rp6), hash(rp7)) 1582 self.assertNotEqual(rp6, rp8) 1583 1584 ms1 = _geo.MultiShape(shapes=(s, s+dxy1)) 1585 ms2 = _geo.MultiShape(shapes=rp1.pointsshapes) 1586 ms3 = _geo.MultiShape(shapes=( 1587 s + i*dxy1 + j*dxy2 for i, j in product(range(2), range(2)) 1588 )) 1589 ms4 = _geo.MultiShape(shapes=rp4.pointsshapes) 1590 rot = _geo.Rotation.MY90 1591 ms5 = _geo.MultiShape(shapes=rp1.rotated(rotation=rot).pointsshapes) 1592 1593 self.assertEqual(ms1, ms2) 1594 self.assertEqual(rp1.bounds, ms1.bounds) 1595 self.assertEqual(ms3, ms4) 1596 self.assertEqual(ms5, ms2.rotated(rotation=rot)) 1597 self.assertEqual(rp4.bounds, ms4.bounds) 1598 1599 self.assertIsInstance(repr(rp1), str) # __repr__ coverage 1600 1601 def test_arrayshape(self): 1602 via = _geo.Rect.from_size(width=1.0, height=1.0) 1603 orig = _geo.Point(x=-1.0, y=-1.0) 1604 dx = 2.0 1605 dxy_x = _geo.Point(x=dx, y=0.0) 1606 dy = 3.0 1607 dxy_y = _geo.Point(x=0.0, y=dy) 1608 1609 with self.assertRaises(ValueError): 1610 _geo.ArrayShape(shape=via, offset0=orig, rows=-1, columns=4) 1611 with self.assertRaises(ValueError): 1612 _geo.ArrayShape(shape=via, offset0=orig, rows=1, columns=1) 1613 with self.assertRaises(ValueError): 1614 _geo.ArrayShape(shape=via, offset0=orig, rows=2, columns=1) 1615 with self.assertRaises(ValueError): 1616 _geo.ArrayShape(shape=via, offset0=orig, rows=1, columns=2) 1617 1618 self.assertEqual( 1619 _geo.ArrayShape(shape=via, offset0=orig, rows=3, columns=1, pitch_x=dx, pitch_y=dy), 1620 _geo.RepeatedShape(shape=via, offset0=orig, n=3, n_dxy=dxy_y), 1621 ) 1622 self.assertEqual( 1623 _geo.ArrayShape(shape=via, offset0=orig, rows=1, columns=2, pitch_x=dx, pitch_y=dy), 1624 _geo.RepeatedShape(shape=via, offset0=orig, n=2, n_dxy=dxy_x), 1625 ) 1626 self.assertEqual( 1627 _geo.ArrayShape(shape=via, offset0=orig, rows=3, columns=2, pitch_x=dx, pitch_y=dy), 1628 _geo.RepeatedShape(shape=via, offset0=orig, n=3, n_dxy=dxy_y, m=2, m_dxy=dxy_x), 1629 ) 1630 1631 ar = _geo.ArrayShape(shape=via, offset0=orig, rows=3, columns=4, pitch_x=dx, pitch_y=dy) 1632 self.assertEqual(ar.rows, 3) 1633 self.assertEqual(ar.columns, 4) 1634 self.assertEqual(ar.pitch_x, dx) 1635 self.assertEqual(ar.pitch_y, dy) 1636 1637 def test_maskshape(self): 1638 p = _geo.Point(x=0.0, y=1.0) 1639 l = _geo.Line(point1=_geo.origin, point2=p) 1640 r1 = _geo.Rect.from_size(width=2.0, height=2.0) 1641 r2 = _geo.Rect.from_size(width=2.0, height=2.0) # Same r1 to test equality 1642 m1 = _msk.DesignMask(name="mask1") 1643 m2 = _msk.DesignMask(name="mask2") 1644 ms1 = _geo.MaskShape(mask=m1, shape=r1) 1645 ms2 = _geo.MaskShape(mask=m2, shape=r1) 1646 ms3 = _geo.MaskShape(mask=m1, shape=r2) 1647 ms4 = _geo.MaskShape(mask=m1, shape=l) 1648 ms5 = _geo.MaskShape(mask=m1, shape=(r1 + p)) 1649 rot = _geo.Rotation.R270 1650 ms6 = _geo.MaskShape(mask=m1, shape=l.rotated(rotation=rot)) 1651 1652 self.assertEqual(ms1.mask, m1) 1653 self.assertEqual(ms1.shape, r1) 1654 self.assertEqual(ms1.bounds, r1) 1655 self.assertNotEqual(ms1, []) 1656 self.assertIsInstance(repr(ms1), str) # coverage of __repr__() 1657 self.assertAlmostEqual(ms1.area, r1.area, 6) 1658 self.assertNotEqual(ms1, ms2) 1659 self.assertEqual(ms1, ms3) 1660 self.assertEqual(hash(ms1), hash(ms3)) 1661 self.assertNotEqual(ms1, ms4) 1662 self.assertEqual(ms1.moved(dxy=p), ms5) 1663 self.assertEqual(ms4.rotated(rotation=rot), ms6) 1664 1665 def test_maskshapes(self): 1666 m1 = _msk.DesignMask(name="mask1") 1667 m2 = _msk.DesignMask(name="mask2") 1668 p = _geo.Point(x=3.0, y=-2.0) 1669 rot = _geo.Rotation.R90 1670 r1 = _geo.Rect(left=-3.0, bottom=-1.0, right=-1.0, top=1.0) 1671 r2 = _geo.Rect(left=1.0, bottom=-1.0, right=3.0, top=1.0) 1672 ms1 = _geo.MaskShape(mask=m1, shape=r1) 1673 ms2 = _geo.MaskShape(mask=m2, shape=r2) 1674 ms3 = _geo.MaskShape(mask=m1, shape=r2) 1675 ms4 = _geo.MaskShape(mask=m1, shape=_geo.MultiShape(shapes=(r1, r2))) 1676 mss1 = _geo.MaskShapes(ms1) 1677 mss2 = _geo.MaskShapes((ms1, ms2)) 1678 mss3 = _geo.MaskShapes((ms1, ms2)) 1679 mss3.move(dxy=p) 1680 mss4 = _geo.MaskShapes(ms1) 1681 mss4.rotate(rotation=rot) 1682 mss5 = _geo.MaskShapes(ms1) 1683 mss5 += ms3 1684 mss6 = _geo.MaskShapes(ms4) 1685 mss7 = _geo.MaskShapes((ms1, ms3)) 1686 1687 self.assertEqual(_util.get_first_of(mss1), ms1) 1688 self.assertEqual(_util.get_last_of(mss2), ms2) 1689 self.assertEqual(mss5, mss6) 1690 self.assertEqual(mss5, mss7) 1691 self.assertEqual(mss2.moved(dxy=p), mss3) 1692 self.assertEqual(mss1.rotated(rotation=rot), mss4) 1693 1694 mss1 += ms2 1695 1696 self.assertEqual(mss1, mss2) 1697 1698 mss2._freeze_() 1699 1700 with self.assertRaises(TypeError): 1701 mss2.move(dxy=p) 1702 with self.assertRaises(TypeError): 1703 mss2.rotate(rotation=rot)