halfedge 0.8.1__py3-none-any.whl → 0.12.0__py3-none-any.whl

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.
halfedge/__init__.py CHANGED
@@ -1,4 +1,8 @@
1
- """Allow modules to be imported from top-level."""
1
+ """Import functions into the package namespace.
2
+
3
+ :author: Shay Hill
4
+ :created: 2026-01-25
5
+ """
2
6
 
3
7
  from halfedge.half_edge_constructors import BlindHalfEdges
4
8
  from halfedge.half_edge_elements import Edge, Face, MeshElementBase, Vert
@@ -23,7 +23,7 @@ then passing that raw data to mesh_from_vr would create a mesh with 6 faces and
23
23
  from __future__ import annotations
24
24
 
25
25
  from contextlib import suppress
26
- from typing import TYPE_CHECKING, Any, TypeVar
26
+ from typing import TYPE_CHECKING, Any, Self, TypeVar
27
27
 
28
28
  from paragraphs import par
29
29
 
@@ -37,8 +37,6 @@ if TYPE_CHECKING:
37
37
 
38
38
  _T = TypeVar("_T")
39
39
 
40
- _TBlindHalfEdges = TypeVar("_TBlindHalfEdges", bound="BlindHalfEdges")
41
-
42
40
 
43
41
  class BlindHalfEdges:
44
42
  """Half-edge structure with no lookups."""
@@ -191,11 +189,11 @@ class BlindHalfEdges:
191
189
 
192
190
  @classmethod
193
191
  def from_vlfi(
194
- cls: type[_TBlindHalfEdges],
192
+ cls,
195
193
  vl: Sequence[Vert],
196
194
  fi: Iterable[tuple[int, ...]],
197
195
  hi: Iterable[tuple[int, ...]] | None = None,
198
- ) -> _TBlindHalfEdges:
196
+ ) -> Self:
199
197
  """Create a set of half edges from a vertex list and face index.
200
198
 
201
199
  :param vl: (vertex list) a seq of vertices
@@ -38,14 +38,15 @@ from __future__ import annotations
38
38
 
39
39
  from contextlib import suppress
40
40
  from itertools import count
41
- from typing import TYPE_CHECKING, Any, Callable, TypeVar
41
+ from typing import TYPE_CHECKING, Any, Self, TypeVar
42
42
 
43
43
  from halfedge.type_attrib import Attrib, ContagionAttrib
44
44
 
45
45
  if TYPE_CHECKING:
46
+ from collections.abc import Callable
47
+
46
48
  from halfedge.half_edge_constructors import BlindHalfEdges
47
49
 
48
- _TMeshElem = TypeVar("_TMeshElem", bound="MeshElementBase")
49
50
 
50
51
  _T = TypeVar("_T")
51
52
 
@@ -141,7 +142,7 @@ class MeshElementBase:
141
142
  except AttributeError:
142
143
  return None
143
144
 
144
- def merge_from(self: _TMeshElem, *elements: _TMeshElem) -> _TMeshElem:
145
+ def merge_from(self, *elements: Self) -> Self:
145
146
  """Fill in missing references from other elements.
146
147
 
147
148
  :param elements: elements to merge from
@@ -165,7 +166,7 @@ class MeshElementBase:
165
166
  self.set_attrib(merged_attrib)
166
167
  return self
167
168
 
168
- def split_from(self: _TMeshElem, element: _TMeshElem) -> _TMeshElem:
169
+ def split_from(self, element: Self) -> Self:
169
170
  """Pass attributes when dividing or altering elements.
170
171
 
171
172
  :param element: element to split from
@@ -182,7 +183,7 @@ class MeshElementBase:
182
183
  self.set_attrib(splitted)
183
184
  return self
184
185
 
185
- def __lt__(self: _TMeshElem, other: _TMeshElem) -> bool:
186
+ def __lt__(self, other: Self) -> bool:
186
187
  """Sort by sn.
187
188
 
188
189
  You'll want to be able to sort Verts at least to make a vlvi (vertex list,
halfedge/py.typed CHANGED
@@ -1 +1,5 @@
1
- #
1
+ """ This file is used to indicate to mypy that the package is typed.
2
+
3
+ Do not delete this comment, because empty files choke
4
+ some cloud drives on sync.
5
+ """
halfedge/type_attrib.py CHANGED
@@ -61,7 +61,7 @@ When assigned to a Vert instance, these will be stored in the Vert instance's
61
61
  from __future__ import annotations
62
62
 
63
63
  from contextlib import suppress
64
- from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar
64
+ from typing import TYPE_CHECKING, Any, Generic, Literal, Self, TypeVar
65
65
 
66
66
  from paragraphs import par
67
67
 
@@ -79,13 +79,13 @@ class StaticAttrib(Generic[_T]):
79
79
  merged or split.
80
80
  """
81
81
 
82
- __slots__ = ("_mesh", "_value")
82
+ __slots__ = ("_mesh", "cached_value")
83
83
 
84
84
  def __new__(
85
- cls: type[_TStaticAttrib],
85
+ cls,
86
86
  value: _T | None = None,
87
87
  mesh: BlindHalfEdges | None = None,
88
- ) -> _TStaticAttrib:
88
+ ) -> Self:
89
89
  """Raise an exception if the attribute is not subclassed."""
90
90
  del value
91
91
  del mesh
@@ -98,7 +98,7 @@ class StaticAttrib(Generic[_T]):
98
98
  self, value: _T | None = None, mesh: BlindHalfEdges | None = None
99
99
  ) -> None:
100
100
  """Set value and mesh."""
101
- self._value = value
101
+ self.cached_value = value
102
102
  self._mesh = mesh
103
103
 
104
104
  def copy_to_element(
@@ -109,7 +109,7 @@ class StaticAttrib(Generic[_T]):
109
109
  :param mesh: BlindHalfEdges instance to which attrib will be assigned.
110
110
  :return: Attrib instance
111
111
  """
112
- return type(self)(self._value, mesh)
112
+ return type(self)(self.cached_value, mesh)
113
113
 
114
114
  @property
115
115
  def value(self) -> _T:
@@ -118,12 +118,12 @@ class StaticAttrib(Generic[_T]):
118
118
  :return: Value of the attribute
119
119
  :raises AttributeError: If no value is set and _infer_value fails
120
120
  """
121
- if self._value is not None:
122
- return self._value
121
+ if self.cached_value is not None:
122
+ return self.cached_value
123
123
  with suppress(NotImplementedError, ValueError):
124
124
  value = self._infer_value()
125
- self._value = value
126
- return self._value
125
+ self.cached_value = value
126
+ return self.cached_value
127
127
  msg = "no value set and failed to infer from 'self.mesh'"
128
128
  raise AttributeError(msg)
129
129
 
@@ -157,16 +157,16 @@ class StaticAttrib(Generic[_T]):
157
157
  implication here is that calculation *cannot* be deferred till after a
158
158
  merge.
159
159
  * The merged method sums areas of the merged triangles at the first and
160
- subsequent mergers, so further triangle area calculations (which
161
- wouldn't work on the merged shapes anyway) are not required.
160
+ subsequent mergers, so further triangle area calculations (which wouldn't
161
+ work on the merged shapes anyway) are not required.
162
162
 
163
163
  If you infer a value, cache it by setting self._value.
164
164
 
165
- If you do not intend to infer values, raise an exception. This exception
166
- should occur *before* an AttributeError is raised for a potentially missing
167
- mesh attribute. It should be clear that _infer_value failed because there
168
- is no provision for inferring this Attrib.value, *not* because the
169
- user failed to set the Attrib property attribute.
165
+ If you do not intend to infer values, raise an exception. This exception should
166
+ occur *before* an AttributeError is raised for a potentially missing mesh
167
+ attribute. It should be clear that _infer_value failed because there is no
168
+ provision for inferring this Attrib.value, *not* because the user failed to set
169
+ the Attrib property attribute.
170
170
  """
171
171
  msg = par(
172
172
  f"""'{type(self).__name__}' has no provision for inferring a value from
@@ -175,31 +175,26 @@ class StaticAttrib(Generic[_T]):
175
175
  raise AttributeError(msg)
176
176
 
177
177
 
178
- _TStaticAttrib = TypeVar("_TStaticAttrib", bound=StaticAttrib[Any])
179
-
180
-
181
178
  class Attrib(Generic[_T]):
182
179
  """Base class for element attributes.
183
180
 
184
181
  MeshElementBase has methods set_attrib and get_attrib that will store Attrib
185
182
  instances in the MeshElemenBase.attrib dict. The Attrib class defines how these
186
- attributes behave when mesh elements are merged and optionally allows a value
187
- (e.g., edge length) to be inferred from the Attrib.element property when and if
188
- needed, allowing us to cache (and potentially never access) slow attributes.
189
-
190
- Do not overload `__init__` or `value`. For the most part, treat as an ABC with
191
- abstract methods `merge`, `split`, and `_infer_value`--although the base methods
192
- are marginally useful and instructive, so you will not need to overload both in
193
- every case.
183
+ attributes behave when mesh elements are merged and optionally allows a value (e.g.,
184
+ edge length) to be inferred from the Attrib.element property when and if needed,
185
+ allowing us to cache (and potentially never access) slow attributes.
186
+
187
+ Do not overload `__init__` or `value`. Subclasses can override `merge`, `split`,
188
+ and `_infer_value` to customize behavior while inheriting all other functionality.
194
189
  """
195
190
 
196
- __slots__ = ("_element", "_value")
191
+ __slots__ = ("_element", "cached_value")
197
192
 
198
193
  def __new__(
199
- cls: type[_TAttrib],
194
+ cls,
200
195
  value: _T | None = None,
201
196
  element: MeshElementBase | None = None,
202
- ) -> _TAttrib:
197
+ ) -> Self:
203
198
  """Raise an exception if the attribute is not subclassed."""
204
199
  del value
205
200
  del element
@@ -212,7 +207,7 @@ class Attrib(Generic[_T]):
212
207
  self, value: _T | None = None, element: MeshElementBase | None = None
213
208
  ) -> None:
214
209
  """Set value and element."""
215
- self._value = value
210
+ self.cached_value = value
216
211
  self._element = element
217
212
 
218
213
  @property
@@ -222,12 +217,12 @@ class Attrib(Generic[_T]):
222
217
  :return: Value of the attribute
223
218
  :raises AttributeError: If no value is set and _infer_value fails
224
219
  """
225
- if self._value is not None:
226
- return self._value
220
+ if self.cached_value is not None:
221
+ return self.cached_value
227
222
  with suppress(NotImplementedError, ValueError):
228
223
  value = self._infer_value()
229
- self._value = value
230
- return self._value
224
+ self.cached_value = value
225
+ return self.cached_value
231
226
  msg = "no value set and failed to infer from 'self.element'"
232
227
  raise AttributeError(msg)
233
228
 
@@ -249,7 +244,7 @@ class Attrib(Generic[_T]):
249
244
  :param element: New element
250
245
  :return: Attrib instance
251
246
  """
252
- return type(self)(self._value, element)
247
+ return type(self)(self.cached_value, element)
253
248
 
254
249
  @classmethod
255
250
  def merge(cls, *merge_from: _TAttrib | None) -> _TAttrib | None:
@@ -268,14 +263,15 @@ class Attrib(Generic[_T]):
268
263
  Attrib attributes are assumed None if not defined and are never defined
269
264
  if their value is None.
270
265
 
271
- This base method will not merge attributes, which is desirable in some cases.
266
+ Override this method in subclasses to customize merge behavior. Default
267
+ implementation returns None (no merge), which is desirable in some cases.
272
268
  For example, a triangle circumcenter that will be meaningless when the
273
269
  triangle is merged.
274
270
  """
275
271
  _ = merge_from
276
272
  return None
277
273
 
278
- def split(self: _TAttrib) -> _TAttrib | None:
274
+ def split(self) -> Self | None:
279
275
  """Define how attribute will be passed when dividing self.element.
280
276
 
281
277
  :return: Attrib instance or None
@@ -293,7 +289,9 @@ class Attrib(Generic[_T]):
293
289
  attribute is lazy (e.g., edge norm), you might want to unset _value for each
294
290
  piece of a split edge.
295
291
 
296
- This base method will not pass an attribute when dividing or altering.
292
+ Override this method in subclasses to customize split behavior. Default
293
+ implementation returns None (no split), meaning the attribute will not be
294
+ passed when dividing or altering elements.
297
295
  """
298
296
  return None
299
297
 
@@ -320,11 +318,12 @@ class Attrib(Generic[_T]):
320
318
 
321
319
  If you infer a value, cache it by setting self._value.
322
320
 
323
- If you do not intend to infer values, raise an exception. This exception
324
- should occur *before* an AttributeError is raised for a potentially missing
325
- element attribute. It should be clear that _infer_value failed because there
326
- is no provision for inferring this Attrib.value, *not* because the
327
- user failed to set the Attrib property attribute.
321
+ Override this method in subclasses to customize value inference. If you do
322
+ not intend to infer values, raise an exception. This exception should occur
323
+ *before* an AttributeError is raised for a potentially missing element
324
+ attribute. It should be clear that _infer_value failed because there is no
325
+ provision for inferring this Attrib.value, *not* because the user failed to
326
+ set the Attrib property attribute.
328
327
  """
329
328
  msg = par(
330
329
  f"""'{type(self).__name__}' has no provision for inferring a value from
@@ -348,10 +347,10 @@ class ContagionAttrib(Attrib[Literal[True]]):
348
347
  """
349
348
 
350
349
  def __new__(
351
- cls: type[_TAttrib],
350
+ cls,
352
351
  value: Literal[True] | None = None,
353
352
  element: MeshElementBase | None = None,
354
- ) -> _TAttrib:
353
+ ) -> Self:
355
354
  """Raise an exception if the attribute is not subclassed."""
356
355
  del value
357
356
  del element
@@ -381,7 +380,7 @@ class ContagionAttrib(Attrib[Literal[True]]):
381
380
  return attribs[0]
382
381
  return None
383
382
 
384
- def split(self: _TAttrib) -> _TAttrib | None:
383
+ def split(self) -> Self | None:
385
384
  """Copy attribute to splits.
386
385
 
387
386
  :return: self
@@ -400,10 +399,10 @@ class IncompatibleAttrib(Attrib[_T]):
400
399
  """
401
400
 
402
401
  def __new__(
403
- cls: type[_TAttrib],
402
+ cls,
404
403
  value: _T | None = None,
405
404
  element: MeshElementBase | None = None,
406
- ) -> _TAttrib:
405
+ ) -> Self:
407
406
  """Raise an exception if the attribute is not subclassed."""
408
407
  del value
409
408
  del element
@@ -431,7 +430,7 @@ class IncompatibleAttrib(Attrib[_T]):
431
430
  return None
432
431
  return merge_from[0]
433
432
 
434
- def split(self: _TAttrib) -> _TAttrib | None:
433
+ def split(self) -> Self | None:
435
434
  """Pass the value on.
436
435
 
437
436
  :return: self
@@ -443,10 +442,10 @@ class NumericAttrib(Attrib[_T]):
443
442
  """Average merge_from values."""
444
443
 
445
444
  def __new__(
446
- cls: type[_TAttrib],
445
+ cls,
447
446
  value: _T | None = None,
448
447
  element: MeshElementBase | None = None,
449
- ) -> _TAttrib:
448
+ ) -> Self:
450
449
  """Raise an exception if the attribute is not subclassed."""
451
450
  del value
452
451
  del element
@@ -473,10 +472,10 @@ class Vector2Attrib(Attrib[tuple[float, float]]):
473
472
  """Average merge_from values as xy tuples."""
474
473
 
475
474
  def __new__(
476
- cls: type[_TAttrib],
475
+ cls,
477
476
  value: tuple[float, float] | None = None,
478
477
  element: MeshElementBase | None = None,
479
- ) -> _TAttrib:
478
+ ) -> Self:
480
479
  """Raise an exception if the attribute is not subclassed."""
481
480
  del value
482
481
  del element
@@ -496,7 +495,7 @@ class Vector2Attrib(Attrib[tuple[float, float]]):
496
495
  if not have_values:
497
496
  return None
498
497
  values = [x.value for x in have_values]
499
- sum_x, sum_y = (sum(xs) for xs in zip(*values))
498
+ sum_x, sum_y = (sum(xs) for xs in zip(*values, strict=True))
500
499
  num = len(values)
501
500
  return type(have_values[0])((sum_x / num, sum_y / num))
502
501
 
@@ -505,10 +504,10 @@ class Vector3Attrib(Attrib[tuple[float, float, float]]):
505
504
  """Average merge_from values as xyz tuples."""
506
505
 
507
506
  def __new__(
508
- cls: type[_TAttrib],
507
+ cls,
509
508
  value: tuple[float, float, float] | None = None,
510
509
  element: MeshElementBase | None = None,
511
- ) -> _TAttrib:
510
+ ) -> Self:
512
511
  """Raise an exception if the attribute is not subclassed."""
513
512
  del value
514
513
  del element
@@ -528,6 +527,6 @@ class Vector3Attrib(Attrib[tuple[float, float, float]]):
528
527
  if not have_values:
529
528
  return None
530
529
  values = [x.value for x in have_values]
531
- sum_x, sum_y, sum_z = (sum(xs) for xs in zip(*values))
530
+ sum_x, sum_y, sum_z = (sum(xs) for xs in zip(*values, strict=True))
532
531
  num = len(values)
533
532
  return type(have_values[0])((sum_x / num, sum_y / num, sum_z / num))
halfedge/validations.py CHANGED
@@ -10,12 +10,12 @@ created: 181127
10
10
  from __future__ import annotations
11
11
 
12
12
  from itertools import chain
13
- from typing import TYPE_CHECKING, Any, Callable, TypeVar
13
+ from typing import TYPE_CHECKING, Any, TypeVar
14
14
 
15
15
  from halfedge.half_edge_elements import Edge, Face, ManifoldMeshError
16
16
 
17
17
  if TYPE_CHECKING:
18
- from collections.abc import Iterator
18
+ from collections.abc import Callable, Iterator
19
19
 
20
20
  from halfedge.half_edge_querries import StaticHalfEdges
21
21
 
@@ -1,19 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: halfedge
3
- Version: 0.8.1
3
+ Version: 0.12.0
4
4
  Summary: A typical half-edge data structure with some padding
5
+ Author: Shay Hill
5
6
  Author-email: Shay Hill <shay_public@hotmail.com>
6
- License: MIT
7
- Requires-Python: >=3.9
7
+ License-Expression: MIT
8
+ Requires-Dist: paragraphs>=1.0.1
9
+ Requires-Python: >=3.11
8
10
  Description-Content-Type: text/markdown
9
- Requires-Dist: paragraphs
10
- Provides-Extra: dev
11
- Requires-Dist: commitizen; extra == "dev"
12
- Requires-Dist: coverage; extra == "dev"
13
- Requires-Dist: pre-commit; extra == "dev"
14
- Requires-Dist: pylint; extra == "dev"
15
- Requires-Dist: pytest; extra == "dev"
16
- Requires-Dist: tox; extra == "dev"
17
11
 
18
12
  # A typical halfedges data structure with some padding
19
13
 
@@ -0,0 +1,11 @@
1
+ halfedge/__init__.py,sha256=1-q2fVFdMh3NWMh9ujuuyAnxNgPW8jIs_XpJ2oU8eFc,703
2
+ halfedge/half_edge_constructors.py,sha256=mozSdN3fBh1EbjKnhR1g9tqePj2LwM6GL1OrUaBocos,8584
3
+ halfedge/half_edge_elements.py,sha256=z5rjlZCVJJOpWUf8oVNvZXgr1x4xhz5033lmsm4UBLk,18466
4
+ halfedge/half_edge_object.py,sha256=A70CVmV44zDi46gbtGHj8dw4Ltshqs56MRn5-78eM20,28771
5
+ halfedge/half_edge_querries.py,sha256=q5xVOFdpw7t6fhs-Z8yF5j3RlG4DOrOgoLbBsAfkm4A,5179
6
+ halfedge/py.typed,sha256=cnjZxaS4F-N8wLWhQK9YvZ-PaN5T1TK94E9VhPT_LGg,155
7
+ halfedge/type_attrib.py,sha256=Giu4ck7shuq2xZoRrDjOMcOGty5v69Q49lo1kz7BJlU,20452
8
+ halfedge/validations.py,sha256=G3YmJnxX5Q_C3Jhbd7740oTngvsOIllfSwLa81jY3d0,4544
9
+ halfedge-0.12.0.dist-info/WHEEL,sha256=e_m4S054HL0hyR3CpOk-b7Q7fDX6BuFkgL5OjAExXas,80
10
+ halfedge-0.12.0.dist-info/METADATA,sha256=T_3wiZ5N5tYrZy3v36L0sjyEVVDmGv8jWpnA4RdsUT4,6779
11
+ halfedge-0.12.0.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: uv 0.9.27
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
@@ -1,12 +0,0 @@
1
- halfedge/__init__.py,sha256=O5tfw3JIBJH83JlpwdYoTNHFsmcg959rAJd6RU51E4s,661
2
- halfedge/half_edge_constructors.py,sha256=0V9xsjPgoA0zKuqUSSLJI1WKhLDJAXHzVWwH2AHZcq4,8686
3
- halfedge/half_edge_elements.py,sha256=oX4ti6f6L1gcYsoP-KGNwdF4-cRpTJeBoMGMTJuIN14,18554
4
- halfedge/half_edge_object.py,sha256=A70CVmV44zDi46gbtGHj8dw4Ltshqs56MRn5-78eM20,28771
5
- halfedge/half_edge_querries.py,sha256=q5xVOFdpw7t6fhs-Z8yF5j3RlG4DOrOgoLbBsAfkm4A,5179
6
- halfedge/py.typed,sha256=MsSFjiLMLJZ7QhUPpVBWKiyDnCzryquRyr329NoCACI,2
7
- halfedge/type_attrib.py,sha256=NP8h5DmkSeq_v-wfueUumFeUKY94XqDoTQR9Lu8n094,20413
8
- halfedge/validations.py,sha256=8dDUf76t1_63_e0V5tRETHJDoYUUmjnSFgsULwSSKP4,4544
9
- halfedge-0.8.1.dist-info/METADATA,sha256=HITY6W4YbbBLTaGm68Iizy9tDtaMxmDyXj5gyYp4PKo,6996
10
- halfedge-0.8.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
- halfedge-0.8.1.dist-info/top_level.txt,sha256=iqphKHiIR4DfFMs14-gkFOWTXVkRWfGmDZmCYHhuoFM,9
12
- halfedge-0.8.1.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- halfedge