deoverlap 3.2.1__tar.gz → 3.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {deoverlap-3.2.1/src/deoverlap.egg-info → deoverlap-3.2.2}/PKG-INFO +1 -1
- {deoverlap-3.2.1 → deoverlap-3.2.2}/pyproject.toml +1 -1
- {deoverlap-3.2.1 → deoverlap-3.2.2}/src/deoverlap/__init__.py +1 -1
- {deoverlap-3.2.1 → deoverlap-3.2.2}/src/deoverlap/deoverlap.py +19 -2
- {deoverlap-3.2.1 → deoverlap-3.2.2/src/deoverlap.egg-info}/PKG-INFO +1 -1
- {deoverlap-3.2.1 → deoverlap-3.2.2}/tests/test_deoverlap.py +29 -0
- {deoverlap-3.2.1 → deoverlap-3.2.2}/LICENSE +0 -0
- {deoverlap-3.2.1 → deoverlap-3.2.2}/README.md +0 -0
- {deoverlap-3.2.1 → deoverlap-3.2.2}/setup.cfg +0 -0
- {deoverlap-3.2.1 → deoverlap-3.2.2}/src/deoverlap/vpype_plugin.py +0 -0
- {deoverlap-3.2.1 → deoverlap-3.2.2}/src/deoverlap.egg-info/SOURCES.txt +0 -0
- {deoverlap-3.2.1 → deoverlap-3.2.2}/src/deoverlap.egg-info/dependency_links.txt +0 -0
- {deoverlap-3.2.1 → deoverlap-3.2.2}/src/deoverlap.egg-info/entry_points.txt +0 -0
- {deoverlap-3.2.1 → deoverlap-3.2.2}/src/deoverlap.egg-info/requires.txt +0 -0
- {deoverlap-3.2.1 → deoverlap-3.2.2}/src/deoverlap.egg-info/top_level.txt +0 -0
- {deoverlap-3.2.1 → deoverlap-3.2.2}/tests/test_vpype_plugin.py +0 -0
|
@@ -69,6 +69,7 @@ class _SegId:
|
|
|
69
69
|
index: int
|
|
70
70
|
count: int
|
|
71
71
|
closed: bool
|
|
72
|
+
heading: float = 0.0 # direction of travel, radians
|
|
72
73
|
|
|
73
74
|
|
|
74
75
|
@dataclass
|
|
@@ -189,6 +190,17 @@ def _seg_adjacent(a: _SegId, b: _SegId, window: int) -> bool:
|
|
|
189
190
|
return False
|
|
190
191
|
|
|
191
192
|
|
|
193
|
+
def _heading(a: tuple[float, float], b: tuple[float, float]) -> float:
|
|
194
|
+
return math.atan2(b[1] - a[1], b[0] - a[0])
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _folds_back(a: _SegId, b: _SegId, angle_tol_rad: float) -> bool:
|
|
198
|
+
"""True if the chain doubles back: headings nearly opposite (a hairpin)."""
|
|
199
|
+
d = abs(a.heading - b.heading) % (2 * math.pi)
|
|
200
|
+
d = min(d, 2 * math.pi - d)
|
|
201
|
+
return d > math.pi - angle_tol_rad
|
|
202
|
+
|
|
203
|
+
|
|
192
204
|
def _reassemble(parts: Sequence[BaseGeometry], original: BaseGeometry) -> BaseGeometry:
|
|
193
205
|
clean = [p for p in parts if p is not None and not p.is_empty]
|
|
194
206
|
if not clean:
|
|
@@ -200,6 +212,10 @@ def _reassemble(parts: Sequence[BaseGeometry], original: BaseGeometry) -> BaseGe
|
|
|
200
212
|
and merged.equals(original.boundary)
|
|
201
213
|
):
|
|
202
214
|
return original
|
|
215
|
+
if merged.geom_type == "MultiLineString":
|
|
216
|
+
# Rejoin pieces that meet end to end: the arc through a ring's start
|
|
217
|
+
# vertex, or consecutive edges in segments mode.
|
|
218
|
+
merged = line_merge(merged)
|
|
203
219
|
return merged
|
|
204
220
|
|
|
205
221
|
|
|
@@ -285,7 +301,7 @@ def _explode_segments(
|
|
|
285
301
|
continue
|
|
286
302
|
segments.append(LineString([a, b]))
|
|
287
303
|
origins.append(origin)
|
|
288
|
-
seg_ids.append(_SegId(chain, i, count, True))
|
|
304
|
+
seg_ids.append(_SegId(chain, i, count, True, _heading(a, b)))
|
|
289
305
|
parent_lengths.append(parent_len)
|
|
290
306
|
else:
|
|
291
307
|
count = len(coords) - 1
|
|
@@ -295,7 +311,7 @@ def _explode_segments(
|
|
|
295
311
|
continue
|
|
296
312
|
segments.append(LineString([a, b]))
|
|
297
313
|
origins.append(origin)
|
|
298
|
-
seg_ids.append(_SegId(chain, i, count, False))
|
|
314
|
+
seg_ids.append(_SegId(chain, i, count, False, _heading(a, b)))
|
|
299
315
|
parent_lengths.append(parent_len)
|
|
300
316
|
chain += 1
|
|
301
317
|
|
|
@@ -407,6 +423,7 @@ class _MaskIndex:
|
|
|
407
423
|
seg_id is not None
|
|
408
424
|
and other_seg is not None
|
|
409
425
|
and _seg_adjacent(seg_id, other_seg, self.segment_adjacency)
|
|
426
|
+
and not _folds_back(seg_id, other_seg, angle_tol_rad)
|
|
410
427
|
):
|
|
411
428
|
continue
|
|
412
429
|
if self.parallel_only and angle is not None:
|
|
@@ -108,6 +108,35 @@ def test_group_keeps_split_ring_as_one_multipart():
|
|
|
108
108
|
assert len(flat_result.kept) >= 2
|
|
109
109
|
|
|
110
110
|
|
|
111
|
+
def test_cut_ring_joins_across_its_start_point():
|
|
112
|
+
"""The arc through the ring's first/last vertex is one piece, not two."""
|
|
113
|
+
ring = LineString([(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)])
|
|
114
|
+
cutter = LineString([(1, -1), (1, 3)])
|
|
115
|
+
grouped = deoverlap([cutter, ring], 0.15, group=True, keep=KeepPolicy.FIRST)
|
|
116
|
+
assert len(flatten_geometries(grouped.kept_parts[1])) == 2
|
|
117
|
+
flat = deoverlap([cutter, ring], 0.15, group=False, keep=KeepPolicy.FIRST)
|
|
118
|
+
assert len(flat.kept) == 3 # cutter + two arcs
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_segments_removes_fold_back_between_neighbours():
|
|
122
|
+
"""A hairpin folds onto itself; neighbouring edges must still suppress."""
|
|
123
|
+
spike = LineString([(0, 0), (6, 0), (6.5, 0.4), (6, 0.08), (0, 0.08), (0, 0)])
|
|
124
|
+
result = deoverlap(
|
|
125
|
+
[spike], 0.1, segments=True, parallel_only=True, keep="first",
|
|
126
|
+
min_length=0.05, keep_duplicates=True,
|
|
127
|
+
)
|
|
128
|
+
kept = result.kept_parts[0]
|
|
129
|
+
back = LineString([(6.5, 0.4), (6, 0.08)])
|
|
130
|
+
assert kept.intersection(back.buffer(0.01)).length < 0.1
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_segments_keeps_ordinary_corners():
|
|
134
|
+
"""A 90 degree corner between neighbours is not a fold-back."""
|
|
135
|
+
square = LineString([(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)])
|
|
136
|
+
result = deoverlap([square], 0.3, segments=True, parallel_only=True)
|
|
137
|
+
assert result.kept_parts[0].length == pytest.approx(square.length, abs=0.05)
|
|
138
|
+
|
|
139
|
+
|
|
111
140
|
def test_parallel_only_preserves_crossing():
|
|
112
141
|
horizontal = LineString([(0, 0), (4, 0)])
|
|
113
142
|
vertical = LineString([(2, -2), (2, 2)])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|