offset-poly 0.4.0__tar.gz → 0.6.0__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.
- {offset_poly-0.4.0 → offset_poly-0.6.0}/PKG-INFO +3 -3
- {offset_poly-0.4.0 → offset_poly-0.6.0}/pyproject.toml +8 -6
- {offset_poly-0.4.0 → offset_poly-0.6.0}/src/offset_poly/offset.py +3 -2
- {offset_poly-0.4.0 → offset_poly-0.6.0}/src/offset_poly/offset_corner.py +42 -29
- {offset_poly-0.4.0 → offset_poly-0.6.0}/src/offset_poly/prepare_poly.py +2 -1
- {offset_poly-0.4.0 → offset_poly-0.6.0}/README.md +0 -0
- {offset_poly-0.4.0 → offset_poly-0.6.0}/src/offset_poly/__init__.py +0 -0
- {offset_poly-0.4.0 → offset_poly-0.6.0}/src/offset_poly/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: offset-poly
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: offset a polyline or polygon in 2d space
|
|
5
5
|
Author: Shay Hill
|
|
6
6
|
Author-email: Shay Hill <shay_public@hotmail.com>
|
|
@@ -9,14 +9,14 @@ Classifier: Development Status :: 2 - Pre-Alpha
|
|
|
9
9
|
Classifier: Intended Audience :: Developers
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
17
17
|
Classifier: Typing :: Typed
|
|
18
18
|
Requires-Dist: vec2-math
|
|
19
|
-
Requires-Python: >=3.
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
|
|
22
22
|
# offset_poly
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "offset-poly"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.0"
|
|
4
4
|
description = "offset a polyline or polygon in 2d space"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
7
7
|
authors = [
|
|
8
8
|
{ name = "Shay Hill", email = "shay_public@hotmail.com" }
|
|
9
9
|
]
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
11
|
classifiers = [
|
|
12
12
|
"Development Status :: 2 - Pre-Alpha",
|
|
13
13
|
"Intended Audience :: Developers",
|
|
14
14
|
"License :: OSI Approved :: MIT License",
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
16
|
-
"Programming Language :: Python :: 3.10",
|
|
17
16
|
"Programming Language :: Python :: 3.11",
|
|
18
17
|
"Programming Language :: Python :: 3.12",
|
|
19
18
|
"Programming Language :: Python :: 3.13",
|
|
20
19
|
"Programming Language :: Python :: 3.14",
|
|
20
|
+
"Programming Language :: Python :: 3.15",
|
|
21
21
|
"Typing :: Typed",
|
|
22
22
|
]
|
|
23
23
|
dependencies = ["vec2_math"]
|
|
@@ -36,7 +36,7 @@ dev = [
|
|
|
36
36
|
|
|
37
37
|
[tool.commitizen]
|
|
38
38
|
name = "cz_conventional_commits"
|
|
39
|
-
version = "0.
|
|
39
|
+
version = "0.6.0"
|
|
40
40
|
tag_format = "$version"
|
|
41
41
|
major-version-zero = true
|
|
42
42
|
version_files = ["pyproject.toml:^version"]
|
|
@@ -54,7 +54,7 @@ log_cli = true
|
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
[tool.ruff]
|
|
57
|
-
target-version = "
|
|
57
|
+
target-version = "py311"
|
|
58
58
|
exclude = ["typings", ".venv"]
|
|
59
59
|
[tool.ruff.lint.pydocstyle]
|
|
60
60
|
convention = "pep257"
|
|
@@ -86,6 +86,7 @@ ignore = [
|
|
|
86
86
|
"FLY", # Use f-string instead of ...
|
|
87
87
|
"S311", # Standard pseudo-random generators are not suitable for security/cryptographic purposes
|
|
88
88
|
"S301", # don't use pickle
|
|
89
|
+
"CPY001", # copyrite notice should be present in each file
|
|
89
90
|
]
|
|
90
91
|
|
|
91
92
|
|
|
@@ -94,7 +95,7 @@ include = ["src", "tests"]
|
|
|
94
95
|
exclude = ["**/__pycache__.py"]
|
|
95
96
|
ignore = ["typings", ".venv"]
|
|
96
97
|
|
|
97
|
-
pythonVersion = "3.
|
|
98
|
+
pythonVersion = "3.11"
|
|
98
99
|
pythonPlatform = "All"
|
|
99
100
|
|
|
100
101
|
typeCheckingMode = "strict"
|
|
@@ -110,3 +111,4 @@ venv = "./.venv"
|
|
|
110
111
|
|
|
111
112
|
[[tool.pyright.executionEnvironments]]
|
|
112
113
|
root = "tests"
|
|
114
|
+
extraPaths = ["."]
|
|
@@ -8,6 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import enum
|
|
10
10
|
import itertools as it
|
|
11
|
+
from collections.abc import Iterable
|
|
11
12
|
from typing import TYPE_CHECKING
|
|
12
13
|
|
|
13
14
|
from vec2_math import vadd, vsub
|
|
@@ -19,9 +20,9 @@ from offset_poly.prepare_poly import (
|
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
if TYPE_CHECKING:
|
|
22
|
-
from collections.abc import
|
|
23
|
+
from collections.abc import Sequence
|
|
23
24
|
|
|
24
|
-
_Vec2 = tuple[float, float]
|
|
25
|
+
_Vec2 = tuple[float, float] | Iterable[float]
|
|
25
26
|
|
|
26
27
|
_MIN_PTS_FOR_POLYGON = 3
|
|
27
28
|
_MIN_PTS_FOR_POLYLINE = 2
|
|
@@ -6,27 +6,29 @@
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
import dataclasses
|
|
10
9
|
import math
|
|
10
|
+
from collections.abc import Iterable
|
|
11
11
|
|
|
12
12
|
import vec2_math as v2
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
_Seg = tuple[
|
|
14
|
+
_Vec2 = tuple[float, float] | Iterable[float]
|
|
15
|
+
_Seg = tuple[_Vec2, _Vec2] | Iterable[Iterable[float]]
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def _offset_seg(
|
|
18
|
+
def _offset_seg(
|
|
19
|
+
seg: _Seg, gap: float
|
|
20
|
+
) -> tuple[tuple[float, float], tuple[float, float]]:
|
|
19
21
|
"""Offset a line segment by a gap.
|
|
20
22
|
|
|
21
23
|
:param seg: line segment
|
|
22
24
|
:param gap: gap to offset by
|
|
23
25
|
:return: line segment moved gap distance to the left
|
|
24
26
|
"""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
vec_a, vec_b = seg
|
|
28
|
+
vec_left = v2.set_norm(v2.qrotate(v2.vsub(vec_b, vec_a), 1), gap)
|
|
29
|
+
return v2.vadd(vec_a, vec_left), v2.vadd(vec_b, vec_left)
|
|
27
30
|
|
|
28
31
|
|
|
29
|
-
@dataclasses.dataclass
|
|
30
32
|
class GapCorner:
|
|
31
33
|
"""Offset a corner defined by three points.
|
|
32
34
|
|
|
@@ -42,36 +44,44 @@ class GapCorner:
|
|
|
42
44
|
Calculate quadratic Bezier control points for a rounded corner at abc
|
|
43
45
|
"""
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"""
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
pnt_a: _Vec2,
|
|
50
|
+
pnt_b: _Vec2,
|
|
51
|
+
pnt_c: _Vec2,
|
|
52
|
+
gap_1: float,
|
|
53
|
+
gap_2: float | None,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Initialize a GapCorner instance."""
|
|
56
|
+
x, y = pnt_a
|
|
57
|
+
self.pnt_a = x, y
|
|
58
|
+
x, y = pnt_b
|
|
59
|
+
self.pnt_b = x, y
|
|
60
|
+
x, y = pnt_c
|
|
61
|
+
self.pnt_c = x, y
|
|
62
|
+
self.gap_1 = gap_1
|
|
63
|
+
self.gap_2 = gap_2 if gap_2 is not None else gap_1
|
|
54
64
|
vec_ab = v2.vsub(self.pnt_b, self.pnt_a)
|
|
55
65
|
vec_bc = v2.vsub(self.pnt_c, self.pnt_b)
|
|
56
66
|
self.angle = v2.get_signed_angle(vec_ab, vec_bc)
|
|
57
67
|
|
|
58
68
|
@property
|
|
59
|
-
def _ab_left(self) ->
|
|
69
|
+
def _ab_left(self) -> tuple[tuple[float, float], tuple[float, float]]:
|
|
60
70
|
"""Return segment ab offset to the left."""
|
|
61
71
|
return _offset_seg((self.pnt_a, self.pnt_b), self.gap_1)
|
|
62
72
|
|
|
63
73
|
@property
|
|
64
|
-
def _ab_right(self) ->
|
|
74
|
+
def _ab_right(self) -> tuple[tuple[float, float], tuple[float, float]]:
|
|
65
75
|
"""Return segment ab offset to the right."""
|
|
66
76
|
return _offset_seg((self.pnt_a, self.pnt_b), -self.gap_1)
|
|
67
77
|
|
|
68
78
|
@property
|
|
69
|
-
def _bc_left(self) ->
|
|
79
|
+
def _bc_left(self) -> tuple[tuple[float, float], tuple[float, float]]:
|
|
70
80
|
"""Return segment bc offset to the left."""
|
|
71
81
|
return _offset_seg((self.pnt_b, self.pnt_c), self.gap_2 or 0)
|
|
72
82
|
|
|
73
83
|
@property
|
|
74
|
-
def _bc_right(self) ->
|
|
84
|
+
def _bc_right(self) -> tuple[tuple[float, float], tuple[float, float]]:
|
|
75
85
|
"""Return segment bc offset to the right."""
|
|
76
86
|
return _offset_seg((self.pnt_b, self.pnt_c), -self.gap_2 or 0)
|
|
77
87
|
|
|
@@ -85,7 +95,7 @@ class GapCorner:
|
|
|
85
95
|
"""Return True if corner forms a zero-degree angle."""
|
|
86
96
|
return math.isclose(self.angle % (math.pi * 2), math.pi, abs_tol=1e-6)
|
|
87
97
|
|
|
88
|
-
def _get_xsect(self, seg_1: _Seg, seg_2: _Seg) ->
|
|
98
|
+
def _get_xsect(self, seg_1: _Seg, seg_2: _Seg) -> tuple[float, float]:
|
|
89
99
|
"""Return the intersection of two offset segments.
|
|
90
100
|
|
|
91
101
|
:raise ValueError: if segments do not intersect (straight line with two
|
|
@@ -99,7 +109,8 @@ class GapCorner:
|
|
|
99
109
|
if self.gap_1 != self.gap_2:
|
|
100
110
|
msg = "gaps must be equal for straight corners"
|
|
101
111
|
raise ValueError(msg)
|
|
102
|
-
|
|
112
|
+
_, (x, y) = seg_1
|
|
113
|
+
return (x, y)
|
|
103
114
|
line_1, line_2 = (v2.get_standard_form(x) for x in (seg_1, seg_2))
|
|
104
115
|
xsect_ = v2.get_line_intersection(line_1, line_2)
|
|
105
116
|
if xsect_ is None:
|
|
@@ -108,16 +119,16 @@ class GapCorner:
|
|
|
108
119
|
return xsect_
|
|
109
120
|
|
|
110
121
|
@property
|
|
111
|
-
def xsect(self) ->
|
|
122
|
+
def xsect(self) -> tuple[float, float]:
|
|
112
123
|
"""The intersection of left-offset segments ab and bc."""
|
|
113
124
|
return self._get_xsect(self._ab_left, self._bc_left)
|
|
114
125
|
|
|
115
126
|
@property
|
|
116
|
-
def _xsect_right(self) ->
|
|
127
|
+
def _xsect_right(self) -> tuple[float, float]:
|
|
117
128
|
"""The intersection of right-offset segments ab and bc."""
|
|
118
129
|
return self._get_xsect(self._ab_right, self._bc_right)
|
|
119
130
|
|
|
120
|
-
def _get_cp(self, seg: _Seg) ->
|
|
131
|
+
def _get_cp(self, seg: _Seg) -> tuple[float, float]:
|
|
121
132
|
"""Return closest point on seg to xsect."""
|
|
122
133
|
if self._is_straight or self._is_degenerate:
|
|
123
134
|
return self.pnt_b
|
|
@@ -126,23 +137,25 @@ class GapCorner:
|
|
|
126
137
|
return v2.project_to_segment(seg, self._xsect_right)
|
|
127
138
|
|
|
128
139
|
@property
|
|
129
|
-
def _cp_a(self) ->
|
|
140
|
+
def _cp_a(self) -> tuple[float, float]:
|
|
130
141
|
"""Return closest point on ab to xsect."""
|
|
131
142
|
return self._get_cp((self.pnt_a, self.pnt_b))
|
|
132
143
|
|
|
133
144
|
@property
|
|
134
|
-
def _cp_c(self) ->
|
|
145
|
+
def _cp_c(self) -> tuple[float, float]:
|
|
135
146
|
"""Return closest point on bc to xsect."""
|
|
136
147
|
return self._get_cp((self.pnt_b, self.pnt_c))
|
|
137
148
|
|
|
138
149
|
@property
|
|
139
|
-
def cpts(
|
|
150
|
+
def cpts(
|
|
151
|
+
self,
|
|
152
|
+
) -> tuple[tuple[float, float], tuple[float, float], tuple[float, float]]:
|
|
140
153
|
"""Return control points for a Bezier curve at the corner."""
|
|
141
154
|
return self._cp_a, self.pnt_b, self._cp_c
|
|
142
155
|
|
|
143
156
|
|
|
144
157
|
def gap_corner(
|
|
145
|
-
pnt_a:
|
|
158
|
+
pnt_a: _Vec2, pnt_b: _Vec2, pnt_c: _Vec2, gap_1: float, gap_2: float | None = None
|
|
146
159
|
) -> GapCorner:
|
|
147
160
|
"""Offset a corner (to the left) defined by three points.
|
|
148
161
|
|
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import itertools as it
|
|
10
|
+
from collections.abc import Iterable
|
|
10
11
|
from typing import TYPE_CHECKING, TypeVar
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
13
14
|
from collections.abc import Sequence
|
|
14
15
|
|
|
15
|
-
_Vec2 = tuple[float, float]
|
|
16
|
+
_Vec2 = tuple[float, float] | Iterable[float]
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def remove_coincident_adjacent_points(polyline: Sequence[_Vec2]) -> list[_Vec2]:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|