halfedge 0.7.0__py3-none-any.whl → 0.11.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,20 +23,20 @@ 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, Iterable, Sequence, TypeVar
26
+ from typing import TYPE_CHECKING, Any, Self, TypeVar
27
27
 
28
28
  from paragraphs import par
29
29
 
30
30
  from halfedge.half_edge_elements import Edge, Face, ManifoldMeshError, Vert
31
31
 
32
32
  if TYPE_CHECKING:
33
+ from collections.abc import Iterable, Sequence
34
+
33
35
  from halfedge.type_attrib import Attrib, StaticAttrib
34
36
 
35
37
 
36
38
  _T = TypeVar("_T")
37
39
 
38
- _TBlindHalfEdges = TypeVar("_TBlindHalfEdges", bound="BlindHalfEdges")
39
-
40
40
 
41
41
  class BlindHalfEdges:
42
42
  """Half-edge structure with no lookups."""
@@ -189,11 +189,11 @@ class BlindHalfEdges:
189
189
 
190
190
  @classmethod
191
191
  def from_vlfi(
192
- cls: type[_TBlindHalfEdges],
192
+ cls,
193
193
  vl: Sequence[Vert],
194
194
  fi: Iterable[tuple[int, ...]],
195
195
  hi: Iterable[tuple[int, ...]] | None = None,
196
- ) -> _TBlindHalfEdges:
196
+ ) -> Self:
197
197
  """Create a set of half edges from a vertex list and face index.
198
198
 
199
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,
@@ -79,6 +79,13 @@ class HalfEdges(StaticHalfEdges):
79
79
  Able to infer from:
80
80
  * both verts on same face: that face
81
81
  * empty mesh: a new Hole
82
+
83
+ This is only called when inserting an edge into an existing face (or empty
84
+ mesh), so the orig and dest should always be connected to only one face (or
85
+ nothing, which is handled in this method).
86
+
87
+ Once the face is split, the two verts will form an edge that connects to two
88
+ faces.
82
89
  """
83
90
  if not self.edges:
84
91
  return self.new_hole()
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, Tuple, 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__ = ("_value", "_mesh")
82
+ __slots__ = ("_mesh", "_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
@@ -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__ = ("_value", "_element")
191
+ __slots__ = ("_element", "_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
@@ -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
@@ -469,14 +468,14 @@ class NumericAttrib(Attrib[_T]):
469
468
  return type(have_values[0])(sum(values) / len(values))
470
469
 
471
470
 
472
- class Vector2Attrib(Attrib[Tuple[float, float]]):
471
+ 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,19 +495,19 @@ 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
 
503
502
 
504
- class Vector3Attrib(Attrib[Tuple[float, float, float]]):
503
+ 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,11 +10,13 @@ created: 181127
10
10
  from __future__ import annotations
11
11
 
12
12
  from itertools import chain
13
- from typing import TYPE_CHECKING, Any, Callable, Iterator, 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 Callable, Iterator
19
+
18
20
  from halfedge.half_edge_querries import StaticHalfEdges
19
21
 
20
22
  _T = TypeVar("_T")
@@ -1,19 +1,13 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: halfedge
3
- Version: 0.7.0
3
+ Version: 0.11.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.8
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
 
@@ -35,11 +29,11 @@ mesh.remove_edge(edge)
35
29
 
36
30
  ## Particulars
37
31
 
38
- The idea (for 18 years and counting) has been to create an interface that is neither too complex, too verbose, nor too magical. I've been all over the place as to where that line should be. This is my current thinking.
32
+ The idea (for 19 years and counting) has been to create an interface that is neither too complex, too verbose, nor too magical. I've been all over the place as to where that line should be. This is my current thinking.
39
33
 
40
34
  ### Reflection
41
35
 
42
- If you set `edge_a.orig = vert_a`, then the `Edge.vert` setter will *automagically* set `vert_a.edge = edge_a`. This is true for any setter that might otherwise break the mesh. Sometimes, this reflection will happen when it isn't strictly necessary. Imagine you have `face_a` with three edges: `edge_a`, `edge_b` and `edge_c`. `face_a` has a pointer to `edge_a`, but it could point to any of the three edges and still be correct per the requirements of the halfedge data structure. If you directly set `edge_b.face = face_a`, everything would still be correct (`face_a.edge` would still be `edge_a` and that would still be correct), but the `Edge.edge` setter will nevertheless set `face_a.edge = edge_b`.
36
+ If you set `edge_a.orig = vert_a`, then the `Edge.vert` setter will *automagically* set `vert_a.edge = edge_a`. This is true for any setter that might otherwise break the mesh. Sometimes, this reflection will happen when it isn't strictly necessary. Imagine you have `face_a` with three edges: `edge_a`, `edge_b` and `edge_c`. `face_a` has a pointer to `edge_a`, but it could point to any of the three edges and still be correct per the requirements of the halfedge data structure. If you directly set `edge_b.face = face_a`, everything would still be correct (`face_a.edge` would still be `edge_a` and that would still be correct), but the `Edge.edge` setter will nevertheless "reflectively" set `face_a.edge = edge_b`.
43
37
 
44
38
  ### id
45
39
 
@@ -64,50 +58,66 @@ Face(
64
58
 
65
59
  The `is_hole` `__init__` kwarg is shorthand for
66
60
 
67
- class IsHole(ContagionAttribute):
68
- pass
61
+ ```python
62
+ class IsHole(ContagionAttribute):
63
+ pass
64
+
65
+ face = Face()
66
+ face.add_attrib(IsHole())
67
+ ```
69
68
 
70
- vert = Vert()
71
- vert.add_attrib(IsHole())
72
69
 
73
70
  The `face_instance.is_hole` property getter is shorthand for
74
71
 
75
- vert.get_attrib(IsHole())
72
+ ```python
73
+ vert.get_attrib(IsHole())
74
+ ```
76
75
 
77
76
  More on `Attrib` classes below and in `type_attrib.py`.
78
77
 
79
78
  ### Element Attributes
80
79
 
81
- By halfedge convention, each Vert instance holds a pointer to one Edge instance, each Face instance holds a pointer to one Edge instance, and each Edge instance holds four pointers (orig, pair, face, next). These describe the geometry of a mesh, but there may be other attributes you would like to assign to these instances. For example, each Face instance might have a color or each Vert instance an (x, y) coordinate. There is no objectively correct way define these attributes or to combine them when two elements are merged. If you assign position vertices to your verts, will they be xy tuples or numpy arrays? Do a red and a blue face behave like paint and combine to make a purple face? Or do they behave like DNA to make a red *or* blue face depending on which is dominant?
80
+ By halfedge convention
82
81
 
83
- These cannot be stored as simple attributes (e.g., `face.color`), because it wouldn't be clear what to do when two faces were combined--by, for instance, deleting a shared edge. Somewhere, you have to define a rule for how different colored faces merge or how coordinate locations combine when merging verts. So, properties like color must be defined here as `Attrib` instances. To create an attribute, inherit from `Attrib` or one of its children defined in `type_attrib.py`. Define `merge`, `split`, and `_infer_value` methods to determine how (for instance, face color) will behave when merged or cached.
82
+ - each Vert instance holds a pointer to one Edge instance;
83
+ - each Face instance holds a pointer to one Edge instance; and
84
+ - each Edge instance holds four pointers (orig, pair, face, next).
84
85
 
85
- You cannot assign these with `instance.attribute`. Instead assign with `vert.add_attrib(attrib_instance)`. This will add the Attrib to a `attrib` dict in Vert instance dict. Retrieve the value with `vert_instance.get_attrib(attrib_class)`. Everything will be keyed to the class name, so you will need a new ElemAttribBase descendant for each attribute type.
86
+ These describe the geometry of a mesh, but there may be other attributes you would like to assign to these instances. For example, each Face instance might have a color. There is no objectively correct way to define a face color, nor to merge colors when two faces are merged, nor to split a color when faces are split. Do a red and a blue face behave like paint and combine to make a purple face? Or do they behave like DNA to make a red *or* blue face depending on which is dominant? This library will not guess.
86
87
 
87
- class Coordinate(IncompatibleAttribute[Tuple[float, float]]):
88
- pass
88
+ For each such attribute, you will need to define `merge` and `split` methods to explicate how the attribute 1) combines when elements are merged; and 2) behaves when an element is split. Do this by creating a new descendent of `Attrib` or one of `Attrib`'s children defined in `type_attrib.py`. See the docstring in that file for more information.
89
89
 
90
- vert = Vert()
91
- vert.add_attrib(Coordinate((1, 2)))
92
- assert vert.get_attrib(Coordinate).value == (1, 2)
90
+ ```python
91
+ # `Vector2Attrib` is a child of `Attrib` defined in `type_attrib.py`.
92
+ # It defines suitable (YMMV) `merge` (average) and `split` (fail) methods for
93
+ # an (x, y) coordinate.
94
+ class Coordinate(Vector2Attrib):
95
+ pass
96
+
97
+ vert = Vert()
98
+ vert.add_attrib(Coordinate((1, 2)))
99
+ assert vert.get_attrib(Coordinate).value == (1, 2)
100
+ ```
93
101
 
94
- These element attributes can also be passed at `__init__`
102
+ You cannot assign or access these attributes with `vert.attribute`. Instead assign with `vert.add_attrib(attrib_instance)`. Retrieve the value with `vert.get_attrib(attrib_class)`. Everything will be keyed to the class name, so you will need a new ElemAttribBase descendant for each attribute type.
95
103
 
96
- vert = Vert(Coordinate(1, 2))
97
- assert vert.get_attrib(Coordinate).value == (1, 2)
104
+ These element attributes can also be passed at `__init__`
98
105
 
99
- The Attrib classes and merge and split methods that will be called when two elements are merged (e.g., merge two faces when removing the edge between them) or split (e.g., split an edge into two edges).
106
+ ```python
107
+ vert = Vert(Coordinate(1, 2))
108
+ assert vert.get_attrib(Coordinate).value == (1, 2)
109
+ ```
100
110
 
101
111
  ### You Should Know
102
112
 
103
113
  A canonical half-edge data structure stores:
104
114
 
105
- * a set of verts (redundant)
106
- * for each vert, a pointer to an edge
107
- * a set of edges
108
- * for each edge, pointers to vert, pair, face, next
109
- * a set of faces (redundant)
110
- * for each face, a pointer to an edge
115
+ - a set of verts (redundant)
116
+ - for each vert, a pointer to an edge
117
+ - a set of edges
118
+ - for each edge, pointers to vert, pair, face, next
119
+ - a set of faces (redundant)
120
+ - for each face, a pointer to an edge
111
121
 
112
122
  This implementation only stores a set of edges. Sets of verts and faces are generated by iterating through references in edge instances. This makes for slower code, but does not violate DRY and makes for dramatically cleaner code.
113
123
 
@@ -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=GOrO6kiCwLM7XgnonS9jB9Z_6cd6BCm3GsJQ-ASuYIQ,20368
8
+ halfedge/validations.py,sha256=G3YmJnxX5Q_C3Jhbd7740oTngvsOIllfSwLa81jY3d0,4544
9
+ halfedge-0.11.0.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
10
+ halfedge-0.11.0.dist-info/METADATA,sha256=7m3b02UpPq_DYX9V1mZzr3GqerKB-EAla4WlPxB51FM,6779
11
+ halfedge-0.11.0.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.1.0)
2
+ Generator: uv 0.9.26
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=ToGcOYW2b848ynlY59xRNd7ibZuu3OqQpauPCl-N1YE,8654
3
- halfedge/half_edge_elements.py,sha256=oX4ti6f6L1gcYsoP-KGNwdF4-cRpTJeBoMGMTJuIN14,18554
4
- halfedge/half_edge_object.py,sha256=Cc1t_hy2tF52O9iD5tvZN6_w4H7Wt9hwDGFLdBqUxr4,28451
5
- halfedge/half_edge_querries.py,sha256=q5xVOFdpw7t6fhs-Z8yF5j3RlG4DOrOgoLbBsAfkm4A,5179
6
- halfedge/py.typed,sha256=MsSFjiLMLJZ7QhUPpVBWKiyDnCzryquRyr329NoCACI,2
7
- halfedge/type_attrib.py,sha256=1W06nlyTSc0Q6f6PnQec_VNNRLVSgdNStoBlJmMgJNQ,20420
8
- halfedge/validations.py,sha256=FhUv_J7XcaAduyudYGmi7ZL1tEqwJoCxKZwJU2zQolg,4512
9
- halfedge-0.7.0.dist-info/METADATA,sha256=UYimfFRmGpY38Ipy9QgOqWwrW28El8aKnTybjhjKQVc,7411
10
- halfedge-0.7.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
11
- halfedge-0.7.0.dist-info/top_level.txt,sha256=iqphKHiIR4DfFMs14-gkFOWTXVkRWfGmDZmCYHhuoFM,9
12
- halfedge-0.7.0.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- halfedge