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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: offset-poly
3
- Version: 0.4.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.10
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.4.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"
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.4.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 = "py310"
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.10"
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 Iterable, Sequence
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
- _Vec = tuple[float, float]
15
- _Seg = tuple[_Vec, _Vec]
14
+ _Vec2 = tuple[float, float] | Iterable[float]
15
+ _Seg = tuple[_Vec2, _Vec2] | Iterable[Iterable[float]]
16
16
 
17
17
 
18
- def _offset_seg(seg: _Seg, gap: float) -> _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
- vec_left = v2.set_norm(v2.qrotate(v2.vsub(seg[1], seg[0]), 1), gap)
26
- return v2.vadd(seg[0], vec_left), v2.vadd(seg[1], vec_left)
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
- pnt_a: _Vec
46
- pnt_b: _Vec
47
- pnt_c: _Vec
48
- gap_1: float
49
- gap_2: float
50
- angle: float = dataclasses.field(init=False)
51
-
52
- def __post_init__(self) -> None:
53
- """Calculate the angle at b."""
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) -> _Seg:
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) -> _Seg:
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) -> _Seg:
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) -> _Seg:
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) -> _Vec:
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
- return seg_1[1]
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) -> _Vec:
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) -> _Vec:
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) -> _Vec:
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) -> _Vec:
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) -> _Vec:
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(self) -> tuple[_Vec, _Vec, _Vec]:
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: _Vec, pnt_b: _Vec, pnt_c: _Vec, gap_1: float, gap_2: float | None = None
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