Spaces:
Sleeping
Sleeping
File size: 5,345 Bytes
19abe39 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | import numpy as np
from shapely.geometry import LineString, Point, Polygon
from shapely.ops import unary_union
from typing import Optional
from .graph import CreaseGraph, VERTEX_TOL
UNIT_SQUARE_CORNERS = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
_UNIT_SQUARE = Polygon(UNIT_SQUARE_CORNERS)
class PaperState:
"""
Represents the evolving crease pattern on a unit square [0,1]x[0,1].
Uses CreaseGraph for the underlying data structure.
"""
def __init__(self):
self.graph = CreaseGraph()
self.fold_history: list[dict] = []
def anchor_points(self) -> list[tuple[float, float]]:
points: dict[tuple[float, float], None] = {}
for corner in UNIT_SQUARE_CORNERS:
points[corner] = None
for vid, (x, y) in self.graph.vertices.items():
points[(float(x), float(y))] = None
return list(points.keys())
def _is_anchor(self, pt: tuple[float, float]) -> bool:
px, py = pt
for ax, ay in self.anchor_points():
if abs(ax - px) < VERTEX_TOL and abs(ay - py) < VERTEX_TOL:
return True
return False
def add_crease(self, p1: list, p2: list, assignment: str) -> dict:
errors: list[str] = []
if assignment not in ('M', 'V'):
return {
'valid': False,
'anchored': False,
'new_vertices': [],
'errors': ['invalid_assignment'],
}
p1 = (float(p1[0]), float(p1[1]))
p2 = (float(p2[0]), float(p2[1]))
anchored = self._is_anchor(p1) and self._is_anchor(p2)
seg_len = np.hypot(p2[0] - p1[0], p2[1] - p1[1])
if seg_len < VERTEX_TOL:
errors.append('zero_length')
return {'valid': False, 'anchored': anchored, 'new_vertices': [], 'errors': errors}
new_line = LineString([p1, p2])
if not _UNIT_SQUARE.contains(new_line) and not _UNIT_SQUARE.boundary.contains(new_line):
clipped = new_line.intersection(_UNIT_SQUARE)
if clipped.is_empty:
errors.append('outside_bounds')
return {'valid': False, 'anchored': anchored, 'new_vertices': [], 'errors': errors}
intersection_points: list[tuple[float, float]] = []
for eid, (ev1, ev2, _) in list(self.graph.edges.items()):
ex1, ey1 = self.graph.vertices[ev1]
ex2, ey2 = self.graph.vertices[ev2]
existing_line = LineString([(ex1, ey1), (ex2, ey2)])
inter = new_line.intersection(existing_line)
if inter.is_empty:
continue
if inter.geom_type == 'Point':
ix, iy = inter.x, inter.y
ep1 = (ex1, ey1)
ep2 = (ex2, ey2)
if (
abs(ix - ep1[0]) < VERTEX_TOL and abs(iy - ep1[1]) < VERTEX_TOL
or abs(ix - ep2[0]) < VERTEX_TOL and abs(iy - ep2[1]) < VERTEX_TOL
):
continue
intersection_points.append((ix, iy))
# MultiPoint or LineString intersections (collinear) are skipped
new_vertex_coords: list[tuple[float, float]] = []
for ix, iy in intersection_points:
before = set(self.graph.vertices.keys())
vid = self.graph.add_vertex(ix, iy)
if vid not in before:
new_vertex_coords.append((ix, iy))
for eid in list(self.graph.edges.keys()):
if eid not in self.graph.edges:
continue
ev1, ev2, _ = self.graph.edges[eid]
ex1, ey1 = self.graph.vertices[ev1]
ex2, ey2 = self.graph.vertices[ev2]
seg = LineString([(ex1, ey1), (ex2, ey2)])
pt = Point(ix, iy)
if seg.distance(pt) < VERTEX_TOL:
if ev1 != vid and ev2 != vid:
self.graph.split_edge(eid, vid)
v1_id = self.graph.add_vertex(p1[0], p1[1])
v2_id = self.graph.add_vertex(p2[0], p2[1])
waypoints = [p1] + sorted(
intersection_points,
key=lambda pt: np.hypot(pt[0] - p1[0], pt[1] - p1[1]),
) + [p2]
waypoint_ids = []
for wp in waypoints:
wid = self.graph.add_vertex(wp[0], wp[1])
waypoint_ids.append(wid)
for i in range(len(waypoint_ids) - 1):
wa = waypoint_ids[i]
wb = waypoint_ids[i + 1]
if wa != wb:
self.graph.add_edge(wa, wb, assignment)
record = {
'p1': p1,
'p2': p2,
'assignment': assignment,
'anchored': anchored,
'new_vertices': new_vertex_coords,
}
self.fold_history.append(record)
return {
'valid': True,
'anchored': anchored,
'new_vertices': new_vertex_coords,
'errors': errors,
}
def crease_edges(self) -> list[dict]:
result = []
for eid in self.graph.crease_edges():
v1, v2, assignment = self.graph.edges[eid]
x1, y1 = self.graph.vertices[v1]
x2, y2 = self.graph.vertices[v2]
result.append({'v1': (x1, y1), 'v2': (x2, y2), 'assignment': assignment})
return result
|