deoverlap 3.2.0__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.0/src/deoverlap.egg-info → deoverlap-3.2.2}/PKG-INFO +11 -9
- {deoverlap-3.2.0 → deoverlap-3.2.2}/README.md +10 -8
- {deoverlap-3.2.0 → deoverlap-3.2.2}/pyproject.toml +1 -1
- {deoverlap-3.2.0 → deoverlap-3.2.2}/src/deoverlap/__init__.py +1 -1
- {deoverlap-3.2.0 → deoverlap-3.2.2}/src/deoverlap/deoverlap.py +19 -2
- {deoverlap-3.2.0 → deoverlap-3.2.2/src/deoverlap.egg-info}/PKG-INFO +11 -9
- {deoverlap-3.2.0 → deoverlap-3.2.2}/tests/test_deoverlap.py +29 -0
- {deoverlap-3.2.0 → deoverlap-3.2.2}/LICENSE +0 -0
- {deoverlap-3.2.0 → deoverlap-3.2.2}/setup.cfg +0 -0
- {deoverlap-3.2.0 → deoverlap-3.2.2}/src/deoverlap/vpype_plugin.py +0 -0
- {deoverlap-3.2.0 → deoverlap-3.2.2}/src/deoverlap.egg-info/SOURCES.txt +0 -0
- {deoverlap-3.2.0 → deoverlap-3.2.2}/src/deoverlap.egg-info/dependency_links.txt +0 -0
- {deoverlap-3.2.0 → deoverlap-3.2.2}/src/deoverlap.egg-info/entry_points.txt +0 -0
- {deoverlap-3.2.0 → deoverlap-3.2.2}/src/deoverlap.egg-info/requires.txt +0 -0
- {deoverlap-3.2.0 → deoverlap-3.2.2}/src/deoverlap.egg-info/top_level.txt +0 -0
- {deoverlap-3.2.0 → deoverlap-3.2.2}/tests/test_vpype_plugin.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deoverlap
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: De-overlap Shapely geometries that sit within a tolerance of each other.
|
|
5
5
|
Author-email: Pietro Leoni <pietro.leoni@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -51,12 +51,14 @@ Dynamic: license-file
|
|
|
51
51
|
|
|
52
52
|
De-overlap Shapely geometries that sit within a tolerance of each other.
|
|
53
53
|
|
|
54
|
+
Source, examples and issues: [github.com/piLeoni/deoverlap](https://github.com/piLeoni/deoverlap)
|
|
55
|
+
|
|
54
56
|
The common case is pen-plotter work: two strokes closer than a pen width
|
|
55
57
|
visually merge on paper, so only one of them should keep the ink. Unlike
|
|
56
58
|
endpoint-only “deduplicate” tools, this library builds a **corridor** around
|
|
57
59
|
each kept stroke and crops (or drops) later strokes that fall inside it.
|
|
58
60
|
|
|
59
|
-

|
|
61
|
+

|
|
60
62
|
|
|
61
63
|
In every figure, blue is kept, red is removed and the thin orange outline is
|
|
62
64
|
the corridor mask. They are rendered by `examples/make_figures.py`.
|
|
@@ -96,7 +98,7 @@ When two corridors collide, priority is explicit:
|
|
|
96
98
|
| `longest` | Prefer the stroke that covers more ground |
|
|
97
99
|
| `shortest` | Prefer short marks / detail |
|
|
98
100
|
|
|
99
|
-

|
|
101
|
+

|
|
100
102
|
|
|
101
103
|
Original indices are preserved in `result.kept_parts` / `removed_parts` even
|
|
102
104
|
when processing order changes.
|
|
@@ -107,7 +109,7 @@ when processing order changes.
|
|
|
107
109
|
- `mode="drop"` — if more than `drop_fraction` (default 0.5) of a geometry’s
|
|
108
110
|
length is covered, discard the whole thing instead of leaving stubs.
|
|
109
111
|
|
|
110
|
-

|
|
112
|
+

|
|
111
113
|
|
|
112
114
|
`min_length=` drops lineal fragments shorter than that threshold after clipping
|
|
113
115
|
(points are never removed by it).
|
|
@@ -138,7 +140,7 @@ whatever direction its two ends point in.
|
|
|
138
140
|
Raising it to 45° or 60° trims steeper merges too. Crossings steeper than the
|
|
139
141
|
threshold are never cut.
|
|
140
142
|
|
|
141
|
-

|
|
143
|
+

|
|
142
144
|
|
|
143
145
|
## Groups — split pieces stay one object
|
|
144
146
|
|
|
@@ -157,7 +159,7 @@ result.kept_parts[1] # MultiLineString of both arcs
|
|
|
157
159
|
Set `group=False` for a flat list of primitive pieces (origins still recorded
|
|
158
160
|
in `kept_parts`).
|
|
159
161
|
|
|
160
|
-

|
|
162
|
+

|
|
161
163
|
|
|
162
164
|
This is the right model for layer-aware pipelines too: treat each input
|
|
163
165
|
geometry as a group, run deoverlap per layer, and never let one layer’s
|
|
@@ -280,7 +282,7 @@ Cutting leaves fragments; almost every piece under 1 mm on this card is one.
|
|
|
280
282
|
to 860 and the drawn length drops by 35%. For the more conservative default
|
|
281
283
|
(`--parallel-angle 30`, crossings left alone), the same card loses about 21%.
|
|
282
284
|
|
|
283
|
-

|
|
285
|
+

|
|
284
286
|
|
|
285
287
|
In the zoom, the ink panels are blended like real ink: every pass multiplies
|
|
286
288
|
the colour, so the darker the blue, the more times the pen went over the same
|
|
@@ -288,7 +290,7 @@ spot. Before, the dark bands are doubled carriageways and the dark dots are
|
|
|
288
290
|
junctions, where a round pen tip lands on ink that is already there. After,
|
|
289
291
|
the ink is one even layer.
|
|
290
292
|
|
|
291
|
-

|
|
293
|
+

|
|
292
294
|
|
|
293
295
|
To try another place (needs `pip install osmnx`):
|
|
294
296
|
|
|
@@ -320,7 +322,7 @@ its own corridor unit; opposite sides can suppress each other, while immediate
|
|
|
320
322
|
neighbours on the same chain (`--segment-adjacency`, default 1) stay intact so
|
|
321
323
|
joints are not nibbled.
|
|
322
324
|
|
|
323
|
-

|
|
325
|
+

|
|
324
326
|
|
|
325
327
|
```bash
|
|
326
328
|
vpype read map.svg deoverlap -t 0.15mm --keep longest --segments -l 1 write out.svg
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
De-overlap Shapely geometries that sit within a tolerance of each other.
|
|
4
4
|
|
|
5
|
+
Source, examples and issues: [github.com/piLeoni/deoverlap](https://github.com/piLeoni/deoverlap)
|
|
6
|
+
|
|
5
7
|
The common case is pen-plotter work: two strokes closer than a pen width
|
|
6
8
|
visually merge on paper, so only one of them should keep the ink. Unlike
|
|
7
9
|
endpoint-only “deduplicate” tools, this library builds a **corridor** around
|
|
8
10
|
each kept stroke and crops (or drops) later strokes that fall inside it.
|
|
9
11
|
|
|
10
|
-

|
|
12
|
+

|
|
11
13
|
|
|
12
14
|
In every figure, blue is kept, red is removed and the thin orange outline is
|
|
13
15
|
the corridor mask. They are rendered by `examples/make_figures.py`.
|
|
@@ -47,7 +49,7 @@ When two corridors collide, priority is explicit:
|
|
|
47
49
|
| `longest` | Prefer the stroke that covers more ground |
|
|
48
50
|
| `shortest` | Prefer short marks / detail |
|
|
49
51
|
|
|
50
|
-

|
|
52
|
+

|
|
51
53
|
|
|
52
54
|
Original indices are preserved in `result.kept_parts` / `removed_parts` even
|
|
53
55
|
when processing order changes.
|
|
@@ -58,7 +60,7 @@ when processing order changes.
|
|
|
58
60
|
- `mode="drop"` — if more than `drop_fraction` (default 0.5) of a geometry’s
|
|
59
61
|
length is covered, discard the whole thing instead of leaving stubs.
|
|
60
62
|
|
|
61
|
-

|
|
63
|
+

|
|
62
64
|
|
|
63
65
|
`min_length=` drops lineal fragments shorter than that threshold after clipping
|
|
64
66
|
(points are never removed by it).
|
|
@@ -89,7 +91,7 @@ whatever direction its two ends point in.
|
|
|
89
91
|
Raising it to 45° or 60° trims steeper merges too. Crossings steeper than the
|
|
90
92
|
threshold are never cut.
|
|
91
93
|
|
|
92
|
-

|
|
94
|
+

|
|
93
95
|
|
|
94
96
|
## Groups — split pieces stay one object
|
|
95
97
|
|
|
@@ -108,7 +110,7 @@ result.kept_parts[1] # MultiLineString of both arcs
|
|
|
108
110
|
Set `group=False` for a flat list of primitive pieces (origins still recorded
|
|
109
111
|
in `kept_parts`).
|
|
110
112
|
|
|
111
|
-

|
|
113
|
+

|
|
112
114
|
|
|
113
115
|
This is the right model for layer-aware pipelines too: treat each input
|
|
114
116
|
geometry as a group, run deoverlap per layer, and never let one layer’s
|
|
@@ -231,7 +233,7 @@ Cutting leaves fragments; almost every piece under 1 mm on this card is one.
|
|
|
231
233
|
to 860 and the drawn length drops by 35%. For the more conservative default
|
|
232
234
|
(`--parallel-angle 30`, crossings left alone), the same card loses about 21%.
|
|
233
235
|
|
|
234
|
-

|
|
236
|
+

|
|
235
237
|
|
|
236
238
|
In the zoom, the ink panels are blended like real ink: every pass multiplies
|
|
237
239
|
the colour, so the darker the blue, the more times the pen went over the same
|
|
@@ -239,7 +241,7 @@ spot. Before, the dark bands are doubled carriageways and the dark dots are
|
|
|
239
241
|
junctions, where a round pen tip lands on ink that is already there. After,
|
|
240
242
|
the ink is one even layer.
|
|
241
243
|
|
|
242
|
-

|
|
244
|
+

|
|
243
245
|
|
|
244
246
|
To try another place (needs `pip install osmnx`):
|
|
245
247
|
|
|
@@ -271,7 +273,7 @@ its own corridor unit; opposite sides can suppress each other, while immediate
|
|
|
271
273
|
neighbours on the same chain (`--segment-adjacency`, default 1) stay intact so
|
|
272
274
|
joints are not nibbled.
|
|
273
275
|
|
|
274
|
-

|
|
276
|
+

|
|
275
277
|
|
|
276
278
|
```bash
|
|
277
279
|
vpype read map.svg deoverlap -t 0.15mm --keep longest --segments -l 1 write out.svg
|
|
@@ -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:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deoverlap
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: De-overlap Shapely geometries that sit within a tolerance of each other.
|
|
5
5
|
Author-email: Pietro Leoni <pietro.leoni@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -51,12 +51,14 @@ Dynamic: license-file
|
|
|
51
51
|
|
|
52
52
|
De-overlap Shapely geometries that sit within a tolerance of each other.
|
|
53
53
|
|
|
54
|
+
Source, examples and issues: [github.com/piLeoni/deoverlap](https://github.com/piLeoni/deoverlap)
|
|
55
|
+
|
|
54
56
|
The common case is pen-plotter work: two strokes closer than a pen width
|
|
55
57
|
visually merge on paper, so only one of them should keep the ink. Unlike
|
|
56
58
|
endpoint-only “deduplicate” tools, this library builds a **corridor** around
|
|
57
59
|
each kept stroke and crops (or drops) later strokes that fall inside it.
|
|
58
60
|
|
|
59
|
-

|
|
61
|
+

|
|
60
62
|
|
|
61
63
|
In every figure, blue is kept, red is removed and the thin orange outline is
|
|
62
64
|
the corridor mask. They are rendered by `examples/make_figures.py`.
|
|
@@ -96,7 +98,7 @@ When two corridors collide, priority is explicit:
|
|
|
96
98
|
| `longest` | Prefer the stroke that covers more ground |
|
|
97
99
|
| `shortest` | Prefer short marks / detail |
|
|
98
100
|
|
|
99
|
-

|
|
101
|
+

|
|
100
102
|
|
|
101
103
|
Original indices are preserved in `result.kept_parts` / `removed_parts` even
|
|
102
104
|
when processing order changes.
|
|
@@ -107,7 +109,7 @@ when processing order changes.
|
|
|
107
109
|
- `mode="drop"` — if more than `drop_fraction` (default 0.5) of a geometry’s
|
|
108
110
|
length is covered, discard the whole thing instead of leaving stubs.
|
|
109
111
|
|
|
110
|
-

|
|
112
|
+

|
|
111
113
|
|
|
112
114
|
`min_length=` drops lineal fragments shorter than that threshold after clipping
|
|
113
115
|
(points are never removed by it).
|
|
@@ -138,7 +140,7 @@ whatever direction its two ends point in.
|
|
|
138
140
|
Raising it to 45° or 60° trims steeper merges too. Crossings steeper than the
|
|
139
141
|
threshold are never cut.
|
|
140
142
|
|
|
141
|
-

|
|
143
|
+

|
|
142
144
|
|
|
143
145
|
## Groups — split pieces stay one object
|
|
144
146
|
|
|
@@ -157,7 +159,7 @@ result.kept_parts[1] # MultiLineString of both arcs
|
|
|
157
159
|
Set `group=False` for a flat list of primitive pieces (origins still recorded
|
|
158
160
|
in `kept_parts`).
|
|
159
161
|
|
|
160
|
-

|
|
162
|
+

|
|
161
163
|
|
|
162
164
|
This is the right model for layer-aware pipelines too: treat each input
|
|
163
165
|
geometry as a group, run deoverlap per layer, and never let one layer’s
|
|
@@ -280,7 +282,7 @@ Cutting leaves fragments; almost every piece under 1 mm on this card is one.
|
|
|
280
282
|
to 860 and the drawn length drops by 35%. For the more conservative default
|
|
281
283
|
(`--parallel-angle 30`, crossings left alone), the same card loses about 21%.
|
|
282
284
|
|
|
283
|
-

|
|
285
|
+

|
|
284
286
|
|
|
285
287
|
In the zoom, the ink panels are blended like real ink: every pass multiplies
|
|
286
288
|
the colour, so the darker the blue, the more times the pen went over the same
|
|
@@ -288,7 +290,7 @@ spot. Before, the dark bands are doubled carriageways and the dark dots are
|
|
|
288
290
|
junctions, where a round pen tip lands on ink that is already there. After,
|
|
289
291
|
the ink is one even layer.
|
|
290
292
|
|
|
291
|
-

|
|
293
|
+

|
|
292
294
|
|
|
293
295
|
To try another place (needs `pip install osmnx`):
|
|
294
296
|
|
|
@@ -320,7 +322,7 @@ its own corridor unit; opposite sides can suppress each other, while immediate
|
|
|
320
322
|
neighbours on the same chain (`--segment-adjacency`, default 1) stay intact so
|
|
321
323
|
joints are not nibbled.
|
|
322
324
|
|
|
323
|
-

|
|
325
|
+

|
|
324
326
|
|
|
325
327
|
```bash
|
|
326
328
|
vpype read map.svg deoverlap -t 0.15mm --keep longest --segments -l 1 write out.svg
|
|
@@ -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
|